<template>
  <div class="kam-wrapper grid gap-10px grid-rows-[35fr_65fr] h-full text-white">
    <div class="kam-top grid gap-18px grid-cols-[27fr_40fr_33fr] min-h-0">
      <!-- 风险状态 -->
      <DsPanel title="风险状态">
        <template #default>
          <div class="flex flex-col gap-2 h-full items-end">
            <div
              class="time-tabs flex items-center gap-8px px-10px h-42px rd-6px bg-[url('@/assets/bi_images/group_7767.png')] bg-cover bg-center">
              <div
                v-for="tab in timeTabs"
                :key="tab.value"
                class="tab-item inline-flex flex-none items-center justify-center h-28px px-12px whitespace-nowrap cursor-pointer border border-transparent text-12px transition-all duration-200 text-#d8ecff"
                :class="
                  activeRiskTab === tab.value
                    ? 'border-#30dafe text-#30dafe bg-[linear-gradient(180deg,rgba(48,218,254,0.22)_0%,rgba(48,218,254,0.08)_100%)] shadow-[inset_0_0_0_1px_rgba(48,218,254,0.2)]'
                    : ''
                "
                @click="activeRiskTab = tab.value"
              >
                {{ tab.label }}
              </div>
            </div>
            <div class="flex-1 min-h-0 w-full">
              <el-table
                class="risk-table"
                :data="riskRows"
                height="100%"
                :header-cell-class-name="riskHeaderCellClass"
                :cell-style="{ background: 'transparent', color: '#fff' }"
                :header-row-class-name="() => 'risk-table__header'"
                :row-class-name="() => 'risk-table__row'"
                @row-click="handleRiskRowClick"
              >
                <el-table-column prop="name" label="设备名称" min-width="140"/>
                <el-table-column prop="hl" label="H/L报警" align="center" min-width="100"/>
                <el-table-column prop="hll" label="HH/LL报警" align="center" min-width="100"/>
                <el-table-column prop="lock" label="触发联锁" align="center" min-width="100"/>
              </el-table>
            </div>
          </div>
        </template>
      </DsPanel>

      <!-- 关键工艺报警统计 -->
      <DsPanel title="关键工艺报警统计">
        <template #default>
          <div ref="statWrapRef" class="flex flex-col gap-2 h-full items-end">
            <!-- 时间切换区域 -->
            <div
              ref="statTabsRef"
              class="time-tabs flex items-center gap-8px px-10px h-42px rd-6px bg-[url('@/assets/bi_images/group_7767.png')] bg-cover bg-center"
            >
              <div
                v-for="tab in timeTabs"
                :key="tab.value"
                class="tab-item inline-flex flex-none items-center justify-center h-28px px-12px whitespace-nowrap cursor-pointer border border-transparent text-12px transition-all duration-200 text-#d8ecff"
                :class="
                  activeStatisticTab === tab.value
                    ? 'border-#30dafe text-#30dafe bg-[linear-gradient(180deg,rgba(48,218,254,0.22)_0%,rgba(48,218,254,0.08)_100%)] shadow-[inset_0_0_0_1px_rgba(48,218,254,0.2)]'
                    : ''
                "
                @click="activeStatisticTab = tab.value"
              >
                {{ tab.label }}
              </div>
            </div>
            <!-- 事件区域 -->
            <div
              class="flex flex-col flex-1 min-h-0 w-full p-2"
              :style="{ maxHeight: eventMaxHeight ? eventMaxHeight + 'px' : undefined, overflow: 'auto' }"
            >
              <div
                v-for="(item, idx) in statisticList"
                :key="idx"
                class="grid grid-cols-[auto_1fr_auto] items-center gap-16px cursor-pointer transition-all hover:bg-[rgba(48,218,254,0.1)] p-2 rd-4px"
                :class="{ 'bg-[rgba(48,218,254,0.2)]': selectedCode === item.eventName }"
                @click="handleEventClick(item.eventName)"
              >
                <div class="min-w-80px text-[14px]">{{ item.eventName || `事件${idx + 1}` }}</div>
                <div class="stat-progress-wrap relative overflow-visible">
                  <el-progress class="stat-progress" :percentage="item.percent" :stroke-width="20" :show-text="false"/>
                  <span
                    class="progress-marker absolute top-1/2 -translate-y-1/2 w-[28px] h-[28px] border border-[#0aeefe] rounded-full pointer-events-none inline-flex items-center justify-center z-50"
                    :style="{ left: `calc(${item.percent}% - 14px)` }"
                  >
                    <span
                      class="dot w-[10px] h-[10px] bg-white rounded-full shadow-[0_0_2px_2px_rgba(255,255,255,0.8)]"></span>
                  </span>
                </div>
                <div class="text-[#FCDC00] font-600">{{ item.percent.toFixed(2) }}%</div>
              </div>
            </div>
          </div>
        </template>
      </DsPanel>

      <!-- 实时预警信息 -->
      <DsPanel title="实时预警信息">
        <template #default>
          <div class="relative grid grid-rows-[20%_80%] gap-0 h-full min-h-0 overflow-hidden">
            <!-- 顶部标签（改为横向滚动，无分页） -->
            <div class="relative min-h-0 overflow-hidden py-1">
              <div ref="barrierScrollRef"
                   class="flex items-center gap-16px overflow-x-auto pr-8px scroll-smooth scrollbar-hide">
                <div
                  v-for="barrier in barriers"
                  :key="barrier.id"
                  :ref="(el) => setBarrierItemRef(barrier.id, el as HTMLElement | null)"
                  class="shrink-0 inline-flex items-center justify-center text-center h-36px px-12px cursor-pointer font-700 transition-all duration-200 select-none border-2"
                  :class="{ 'border-[#30DAFE] shadow-[0_0_10px_rgba(48,218,254,0.6)]': activeBarrier === barrier.id }"
                  :style="{
                    backgroundImage: barrier.gradient,
                    color: barrier.textColor,
                    borderColor: activeBarrier === barrier.id ? '#30DAFE' : 'transparent',
                    width: barrierItemWidth + 'px',
                    flex: `0 0 ${barrierItemWidth}px`
                  }"
                  @click="handleBarrierClick(barrier.id)"
                >
                  {{ barrier.name }}
                </div>
              </div>
            </div>
            <!-- 中间：左圆形 + 右信息条与按钮 -->
            <div class="grid grid-cols-[4fr_6fr] gap-20px min-h-0 overflow-hidden p-2">
              <!-- 左侧：圆形模块 -->
              <div class="relative overflow-hidden min-h-0">
                <img :src="activeCircleBg" alt="circle" class="w-full h-full object-contain"/>
                <div class="absolute inset-0 flex flex-col items-center justify-center gap-6px pointer-events-none">
                  <div class="text-20px font-700">{{ activeBarrierInfo.name }}</div>
                  <div class="text-14px" :style="{ color: activeBarrierTextColor }">正在预警</div>
                </div>
              </div>

              <!-- 右侧：信息条 + 原因分析按钮 -->
              <div class="flex flex-col gap-8px px-5 min-h-0 overflow-hidden h-full">
                <div
                  class="grid grid-cols-[auto_1fr] items-center pb-2 gap-12px text-14px text-white bg-[url('@/assets/bi_images/zhuangshi.png')] bg-no-repeat bg-center bg-[length:100%_100%] flex-shrink min-h-0"
                >
                  <span class="inline-flex items-center"><i class="inline-block w-2px h-14px mr-8px translate-y-[1px]"/>开始时间</span>
                  <el-tooltip :content="activeBarrierInfo.startTime" placement="top" :show-after="0">
                    <span
                      class="justify-self-end text-white px-10px pr-10 py-4px border border-[rgba(48,218,254,0.6)] rd-4px min-w-120px text-right whitespace-nowrap overflow-hidden text-ellipsis max-w-full"
                    >{{ activeBarrierInfo.startTime }}</span
                    >
                  </el-tooltip>
                </div>
                <div
                  class="grid grid-cols-[auto_1fr] items-center pb-2 gap-12px text-14px text-white bg-[url('@/assets/bi_images/zhuangshi.png')] bg-no-repeat bg-center bg-[length:100%_100%] flex-shrink min-h-0"
                >
                  <span class="inline-flex items-center"><i class="inline-block w-2px h-14px mr-8px translate-y-[1px]"/>时长</span>
                  <el-tooltip :content="activeBarrierInfo.duration" placement="top" :show-after="0">
                    <span
                      class="justify-self-end text-white px-10px pr-10 py-4px border border-[rgba(48,218,254,0.6)] rd-4px min-w-120px text-right whitespace-nowrap overflow-hidden text-ellipsis max-w-full"
                    >{{ activeBarrierInfo.duration }}</span
                    >
                  </el-tooltip>
                </div>
                <div
                  class="grid grid-cols-[auto_1fr] items-center pb-2 gap-12px text-14px text-white bg-[url('@/assets/bi_images/zhuangshi.png')] bg-no-repeat bg-center bg-[length:100%_100%] flex-shrink min-h-0"
                >
                  <span class="inline-flex items-center"><i class="inline-block w-2px h-14px mr-8px translate-y-[1px]"/>报警描述</span>
                  <el-tooltip :content="activeBarrierInfo.description" placement="top" :show-after="0">
                    <span
                      class="justify-self-end text-white px-10px pr-10 py-4px border border-[rgba(48,218,254,0.6)] rd-4px min-w-120px text-right whitespace-nowrap overflow-hidden text-ellipsis max-w-full"
                    >{{ activeBarrierInfo.description }}</span
                    >
                  </el-tooltip>
                </div>
                <div
                  class="grid grid-cols-[auto_1fr] items-center pb-2 gap-12px text-14px text-white bg-[url('@/assets/bi_images/zhuangshi.png')] bg-no-repeat bg-center bg-[length:100%_100%] flex-shrink min-h-0"
                >
                  <span class="inline-flex items-center"><i class="inline-block w-2px h-14px mr-8px translate-y-[1px]"/>组合报警</span>
                  <el-tooltip :content="activeBarrierInfo.combo" placement="top" :show-after="0">
                    <span
                      class="justify-self-end text-white px-10px pr-10 py-4px border border-[rgba(48,218,254,0.6)] rd-4px min-w-120px text-right whitespace-nowrap overflow-hidden text-ellipsis max-w-full"
                    >{{ activeBarrierInfo.combo }}</span
                    >
                  </el-tooltip>
                </div>

                <!-- 处置建议和原因分析按钮 - 放在信息条下面 -->
                <div class="flex justify-between items-center flex-shrink-0">
                  <button class="analysis-btn" @click="handleSuggestionClick">处置建议</button>
                  <button class="analysis-btn" @click="handleAnalysisClick">原因分析</button>
                </div>
              </div>
            </div>
          </div>
        </template>
      </DsPanel>
    </div>

    <div class="kam-bottom min-h-0" ref="kamBottomRef">
      <DsPanel
        title="预警分析"
        class="relative size-full"
        :class="{
          'fullscreen-panel': isFullscreen,
          'animate__animated': isFullscreen,
          'animate__zoomIn': isFullscreen && !isExitingFullscreen,
          'animate__zoomOut': isExitingFullscreen
        }"
      >
        <template #default>
          <div class="flex flex-col h-full overflow-hidden w-full relative">
            <!-- 顶部筛选：偏差 + 设备（绝对定位于右上角） -->
            <div class="absolute z-10 right-0 flex items-center gap-12px" style="top: 12px">
              <el-select
                v-model="activeDeviation"
                size="small"
                class="device-select !w-180px"
                popper-class="logicflow-device-select"
                clearable
                placeholder="请选择偏差"
              >
                <el-option v-for="opt in deviationOptions" :key="opt.value" :label="opt.label" :value="opt.value"/>
              </el-select>
              <el-select v-model="activeDevice" size="small" class="device-select !w-160px"
                         popper-class="logicflow-device-select">
                <el-option v-for="device in deviceOptions" :key="device.value" :label="device.label"
                           :value="device.value"/>
              </el-select>
            </div>
            <div class="flex-1 relative">
              <div class="logicflow-wrap absolute inset-0" ref="logicFlowRef"/>
              <!-- 缩放和重置按钮组 -->
              <div class="absolute z-10 right-20px bottom-20px flex flex-col gap-8px">
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="toggleFullscreen"
                  :title="isFullscreen ? '退出全屏' : '全屏'"
                >
                  <svg v-if="!isFullscreen" class="w-18px h-18px" viewBox="0 0 24 24" fill="none"
                       xmlns="http://www.w3.org/2000/svg">
                    <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"
                          fill="#30DAFE"/>
                  </svg>
                  <svg v-else class="w-18px h-18px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"
                          fill="#30DAFE"/>
                  </svg>
                </button>
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="resetView"
                  title="重置视图"
                >
                  <svg class="w-18px h-18px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M12 2L8 6H11V11H13V6H16L12 2Z" fill="#30DAFE"/>
                    <path d="M12 22L16 18H13V13H11V18H8L12 22Z" fill="#30DAFE"/>
                    <path d="M2 12L6 8V11H11V13H6V16L2 12Z" fill="#30DAFE"/>
                    <path d="M22 12L18 16V13H13V11H18V8L22 12Z" fill="#30DAFE"/>
                    <circle cx="12" cy="12" r="2" fill="#30DAFE"/>
                  </svg>
                </button>
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="zoomIn"
                  title="放大"
                >
                  <svg class="w-20px h-20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M12 5V19M5 12H19" stroke="#30DAFE" stroke-width="2" stroke-linecap="round"/>
                  </svg>
                </button>
                <button
                  class="zoom-btn w-36px h-36px flex items-center justify-center bg-[rgba(12,37,70,0.9)] border border-[#30DAFE] rd-4px cursor-pointer hover:bg-[rgba(48,124,188,0.4)] transition-colors"
                  @click="zoomOut"
                  title="缩小"
                >
                  <svg class="w-20px h-20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M5 12H19" stroke="#30DAFE" stroke-width="2" stroke-linecap="round"/>
                  </svg>
                </button>
              </div>
            </div>
            <div v-if="showTimeline" class="pt-18px">
              <div ref="timelineRef" class="relative h-44px mx-70px">
                <!-- 横向坐标轴底线（完整，可点击） -->
                <div
                  class="absolute left-0 right-0 h-2px bg-[#5E8DD5] translate-y--1/2 cursor-pointer"
                  :style="{ top: axisTopStyle }"
                  @click="handleTimelineClick"
                  @mousemove="handleTimelineMouseMove"
                  @mouseenter="handleTimelineMouseEnter"
                  @mouseleave="handleTimelineMouseLeave"
                ></div>
                <!-- 横向坐标轴进度线（至当前百分比） -->
                <div
                  class="absolute left-0 h-2px bg-[#5E8DD5] translate-y--1/2 pointer-events-none"
                  :style="{ width: `${timelinePercent}%`, top: axisTopStyle }"
                ></div>
                <!-- hover时显示的时间提示 -->
                <div
                  v-if="showTimelineHover"
                  class="absolute translate-x--1/2 translate-y--1/2 pointer-events-none z-20"
                  :style="{ left: `${timelineHoverPercent}%`, top: axisTopStyle }"
                >
                  <div
                    class="absolute left-1/2 bottom-[calc(100%+8px)] translate-x--1/2 text-12px whitespace-nowrap bg-[rgba(12,37,70,0.95)] border border-[#30DAFE] px-8px py-4px rd-4px"
                  >
                    {{ hoverTimelineTime }}
                  </div>
                  <!-- hover指示线 -->
                  <div class="absolute left-1/2 translate-x--1/2 w-1px h-20px bg-[#30DAFE] opacity-60"
                       :style="{ top: '-10px' }"></div>
                </div>
                <!-- 轴线上刻度线（可点击，只在轴线上方） -->
                <template v-for="(mk, _i) in tickList" :key="_i">
                  <div
                    class="absolute w-2px bg-[#5E8DD5] opacity-70 cursor-pointer hover:opacity-100"
                    :style="{ left: `${mk.pos}%`, top: axisTopStyle, height: `${tickHeight}px`, transform: 'translateX(-50%) translateY(-100%)' }"
                    @click.stop="timelinePercent = mk.pos"
                  ></div>
                </template>

                <!-- 偏差报警开始时间红色箭头标注（仅在选中偏差且有时间轴时显示） -->
                <template v-if="showTimeline && deviationMarkers.length">
                  <div
                    v-for="(m, i) in deviationMarkers"
                    :key="`devmark-${i}`"
                    class="absolute pointer-events-none z-20"
                    :style="{ left: `calc(${m.left}% + ${m.offset}px)`, top: axisTopStyle }"
                  >
                    <!-- 箭头（朝下，指向轴线），以轴心为中心对齐；使用像素位移，避免百分比在 0 高度元素上无效 -->
                    <div class="deviation-arrow"
                         style="position: absolute; left: 0; top: -12px; transform: translateX(-50%)"></div>
                    <!-- 细竖线，对齐轴心，向上延伸，避免与轴线重叠 -->
                    <div
                      class="w-1px bg-[#FF3737] opacity-90"
                      style="position: absolute; left: 0"
                      :style="{ top: '-20px', height: '14px', transform: 'translateX(-50%)' }"
                    ></div>
                  </div>
                </template>
                <!-- 轴心与竖线 -->
                <div
                  class="absolute translate-x--1/2 translate-y--1/2 cursor-pointer z-10"
                  :style="{ left: `${timelinePercent}%`, top: axisTopStyle }"
                  @mousedown="startDragging"
                >
                  <div class="relative w-22px" :style="{ height: `${handleLineHeight}px` }">
                    <!-- 使用图片作竖线与圆点 -->
                    <img :src="group7769" alt=""
                         class="absolute left-1/2 top-1/2 translate-x--1/2 translate-y--1/2 w-22px h-full"/>
                    <!-- 时间标签：绝对定位到竖线顶部上方，不影响居中计算 -->
                    <div
                      class="absolute left-1/2 bottom-[calc(100%+6px)] translate-x--1/2 text-12px whitespace-nowrap pointer-events-none">
                      {{ currentTimelineTime }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </template>
      </DsPanel>
    </div>
  </div>
  <!-- 报警详情弹窗 -->
  <el-dialog v-model="showAlarmDialog" title="报警详情" width="560px" append-to-body class="bi-dialog">
    <div class="analysis-content">
      <div class="row">
        <span class="label">位号</span><span class="value">{{ activeAlarm?.indicatorCode ?? '--' }}</span>
      </div>
      <div class="row"><span class="label">装置信息</span><span class="value">甲醇制氢</span></div>
      <div class="row">
        <span class="label">设备信息</span><span class="value">{{ activeDeviceLabel }}</span>
      </div>
      <div class="row">
        <span class="label">报警值</span><span class="value">{{ activeAlarm?.alarmValue ?? '--' }}</span>
      </div>
      <div class="row">
        <span class="label">开始时间</span><span class="value">{{ activeAlarm?.startTime ?? '--' }}</span>
      </div>
      <div class="row">
        <span class="label">结束时间</span><span class="value">{{ activeAlarmEndTime }}</span>
      </div>
      <div class="row">
        <span class="label">持续时长(秒)</span><span class="value">{{ activeAlarm?.duration ?? '--' }}</span>
      </div>
      <div class="row">
        <span class="label">组合报警</span><span class="value">{{ activeAlarm?.combineAlarm ?? '--' }}</span>
      </div>
      <div class="row">
        <span class="label">描述</span><span class="value">{{ activeAlarm?.description ?? '--' }}</span>
      </div>
      <div class="row">
        <span class="label">原因</span><span class="value">{{ activeAlarm?.reason ?? '--' }}</span>
      </div>
    </div>
    <template #footer>
      <el-button type="primary" @click="showAlarmDialog = false">确定</el-button>
    </template>
  </el-dialog>
  <!-- 原因分析弹窗 -->
  <el-dialog v-model="showAnalysisDialog" title="原因分析" width="520px" append-to-body class="bi-dialog">
    <div class="analysis-content">
      <div class="row">
        <span class="label">设备</span><span class="value">{{ activeBarrierInfo.name }}</span>
      </div>
      <div class="row">
        <span class="label">开始时间</span><span class="value">{{ activeBarrierInfo.startTime }}</span>
      </div>
      <div class="row">
        <span class="label">时长</span><span class="value">{{ activeBarrierInfo.duration }}</span>
      </div>
      <div class="row">
        <span class="label">组合报警</span><span class="value">{{ activeBarrierInfo.combo }}</span>
      </div>
      <div class="row">
        <span class="label">原因分析</span><span class="value">{{ activeBarrierInfo.reason }}</span>
      </div>
    </div>
    <template #footer>
      <el-button type="primary" @click="showAnalysisDialog = false">确定</el-button>
    </template>
  </el-dialog>
  <!-- 处置建议弹窗 -->
  <el-dialog v-model="showSuggestionDialog" title="处置建议" width="520px" append-to-body class="bi-dialog">
    <div class="flex items-center justify-center">
      设备存在故障,请工作人员进行维修
    </div>
    <template #footer>
      <el-button type="primary" @click="showSuggestionDialog = false">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import {computed, nextTick, onMounted, onUnmounted, reactive, ref, watch} from 'vue';
import DsPanel from './DsPanel.vue';
import circleBg from '@/assets/bi_images/circle.png';
import circle1Bg from '@/assets/bi_images/circle_1.png';
import circle3Bg from '@/assets/bi_images/circle_3.png';
import alarmLevel2Bg from '@/assets/bi_images/bim/alarm_level_2.png';
import alarmLevel4Bg from '@/assets/bi_images/bim/alarm_level_4.png';
import alarmLevel5Bg from '@/assets/bi_images/bim/alarm_level_5.png';
import group7769 from '@/assets/bi_images/group_7769.png';
import LogicFlow, {
  type EdgeConfig,
  HtmlNode,
  HtmlNodeModel,
  type NodeConfig,
  PolylineEdge,
  PolylineEdgeModel
} from '@logicflow/core';
import '@logicflow/core/dist/index.css';
import 'animate.css';
import type {TableHeaderCellClassName} from 'element-plus';
import {
  getChainAlarm,
  getEquipmentList,
  getNodeMap,
  getPageAlarmIndicator,
  getRiskStatus,
  getRiskSummary
} from '@/api/ds';
import dayjs from 'dayjs';

const timeTabs = [
  {label: '近八小时', value: 2},
  {label: '近一天', value: 3},
  {label: '近一周', value: 4},
  {label: '近一月', value: 5}
] as const;

const activeRiskTab = ref(timeTabs[0].value);
const activeStatisticTab = ref(timeTabs[0].value);

const riskRows = ref([] as any[]);

// 加载风险状态数据
const loadRiskStatus = async () => {
  try {
    const data = await getRiskStatus(activeRiskTab.value);
    riskRows.value =
      data.data?.map((item) => ({
        id: item.equipId,
        equipId: item.equipId,
        name: item.equipmentName,
        hl: item.hlCount,
        hll: item.hllCount,
        lock: item.chainCount
      })) || [];
  } catch (error) {
    console.error('加载风险状态数据失败:', error);
  }
};

// 监听标签页切换
watch(activeRiskTab, () => {
  loadRiskStatus();
});

const statisticList = ref([] as any[]);

// 加载风险统计数据
const loadRiskSummary = async () => {
  try {
    const res = await getRiskSummary(activeStatisticTab.value);
    statisticList.value =
      res.data?.map((item) => {
        // 处理带百分号的数据，如 "46%" -> 46
        const dataStr = String(item.data || '0');
        const percent = parseFloat(dataStr.replace('%', '')) || 0;
        return {
          eventName: item.eventName,
          percent
        };
      }) || [];
  } catch (error) {
    console.error('加载风险统计数据失败:', error);
  }
};

// 监听风险统计标签页切换
watch(activeStatisticTab, () => {
  loadRiskSummary();
});

// 风险统计 - 事件区域动态最大高度（允许滚动，且不影响时间切换区域）
const statWrapRef = ref(null);
const statTabsRef = ref(null);
const eventMaxHeight = ref(undefined) as any;

function updateEventMaxHeight() {
  const wrap = statWrapRef.value;
  const tabs = statTabsRef.value;
  if (!wrap || !tabs) return;
  // 容器总高度减去 tabs 高度与垂直间隙（gap）
  const total = wrap.clientHeight;
  const tabsH = tabs.offsetHeight;
  const styles = getComputedStyle(wrap);
  const gap = parseFloat(styles.rowGap || '0') || 0; // gap-2 => 8px 等
  eventMaxHeight.value = Math.max(0, total - tabsH - gap);
}

let statResizeObs: ResizeObserver | null = null;
let tabsResizeObs: ResizeObserver | null = null;

onMounted(() => {
  // 初次计算
  updateEventMaxHeight();
  // 监听容器和 tabs 尺寸变化
  statResizeObs = new ResizeObserver(() => updateEventMaxHeight());
  tabsResizeObs = new ResizeObserver(() => updateEventMaxHeight());
  if (statWrapRef.value) statResizeObs.observe(statWrapRef.value);
  if (statTabsRef.value) tabsResizeObs.observe(statTabsRef.value);
  // 窗口尺寸变化
  window.addEventListener('resize', updateEventMaxHeight);
});

onUnmounted(() => {
  window.removeEventListener('resize', updateEventMaxHeight);
  statResizeObs?.disconnect();
  tabsResizeObs?.disconnect();
  statResizeObs = null;
  tabsResizeObs = null;
});

// el-table header cell class for colored titles
const riskHeaderCellClass: TableHeaderCellClassName = ({columnIndex}) => {
  if (columnIndex === 1) return 'risk-hl';
  if (columnIndex === 2) return 'risk-hll';
  if (columnIndex === 3) return 'risk-lock';
  return '';
};

// 点击风险状态某行，联动下方预警分析：
// 1) 设备下拉自动选中该设备；
// 2) 仅刷新对应设备的报警信息（不重新获取节点信息）
async function handleRiskRowClick(row: any) {
  const id = row?.equipId ?? row?.equipmentId ?? row?.id ?? null;
  if (id === null || id === undefined) return;
  const numId = id;
  const prev = activeDevice.value;
  activeDevice.value = numId;
  // 若点击的是当前已选设备，watch 不触发，需手动刷新报警信息
  if (prev === numId) {
    await loadChainAlarm({equipmentId: numId, code: selectedCode.value || undefined});
  }
}

// 告警等级对应的颜色配置（加强版）
const alarmLevelColors = [
  {gradient: 'linear-gradient(0deg, #8b5a6e 0%, #b8586a 30%, transparent 100%)', textColor: '#FFB3B3'}, // 低低 - 粉红色
  {gradient: 'linear-gradient(0deg, #6a6d3a 0%, #b8a73d 30%, transparent 100%)', textColor: '#FFE566'}, // 低 - 黄色
  {gradient: 'linear-gradient(0deg, #8b2c2c 0%, #d94646 30%, transparent 100%)', textColor: '#FF6666'}, // 高高 - 红色
  {gradient: 'linear-gradient(0deg, #6b3fa0 0%, #8f5ac8 30%, transparent 100%)', textColor: '#E0B3FF'}, // 联锁 - 紫色
  {gradient: 'linear-gradient(0deg, #8b5a2c 0%, #d97d3d 30%, transparent 100%)', textColor: '#FFB366'} // 高 - 橙色
];

const barriers = ref([] as any[]);
const activeBarrier = ref(null);
// 最近一次查看/选中的标签ID（用于在未选中任何标签时仍显示上一次信息）
const lastBarrierId = ref(null);
// 横向滚动容器与子项引用
const barrierScrollRef = ref(null);
const barrierItemRefMap = new Map();
const setBarrierItemRef = (id: number, el: HTMLElement | null) => {
  if (el) {
    barrierItemRefMap.set(id, el);
  } else {
    barrierItemRefMap.delete(id);
  }
};

// 将某个标签项滚动至容器中间位置
function scrollBarrierItemIntoCenter(barrierId: number) {
  try {
    const wrap = barrierScrollRef.value;
    const item = barrierItemRefMap.get(barrierId) || null;
    if (!wrap || !item) return;
    const wrapRect = wrap.getBoundingClientRect();
    const itemRect = item.getBoundingClientRect();
    const itemLeftInWrap = itemRect.left - wrapRect.left + wrap.scrollLeft;
    const itemCenterInWrap = itemLeftInWrap + itemRect.width / 2;
    const target = itemCenterInWrap - wrap.clientWidth / 2;
    const maxScroll = Math.max(0, wrap.scrollWidth - wrap.clientWidth);
    const clamped = Math.max(0, Math.min(target, maxScroll));
    wrap.scrollTo({left: clamped, behavior: 'smooth'});
  } catch {
  }
}

// 顶部标签每项的动态宽度（尽可能容纳4项）
const barrierItemWidth = ref(180);
let barrierResizeObs: ResizeObserver | null = null;

function computeBarrierItemWidth() {
  const wrap = barrierScrollRef.value;
  if (!wrap) return;
  const styles = getComputedStyle(wrap);
  const gap = parseFloat((styles as any).columnGap || styles.gap || '0') || 0;
  const paddingLeft = parseFloat(styles.paddingLeft || '0') || 0;
  const paddingRight = parseFloat(styles.paddingRight || '0') || 0;
  const contentWidth = Math.max(0, wrap.clientWidth - paddingLeft - paddingRight);
  // 始终按每行 4 个计算宽度；不足 4 个时留空，不再平均分配
  const columns = 4;
  const totalGap = gap * (columns - 1);
  const rawWidth = columns > 0 ? (contentWidth - totalGap) / columns : contentWidth;
  barrierItemWidth.value = Math.max(1, Math.floor(rawWidth));
}

// 监听容器或数据变化，重新计算宽度
watch(barriers, async () => {
  await nextTick();
  computeBarrierItemWidth();
});
const isManualSelection = ref(false); // 标记是否为手动选择
let resumeTimer: any = null; // 10秒后恢复自动的定时器
// 待聚焦的位号（在加载并渲染后进行视角定位）
const pendingFocusCode = ref(null);

// 加载屏障失效排名数据（一次性获取全部数据）
const loadBarrierData = async () => {
  try {
    // 后端已返回全部数据，这里请求一个足够大的 pageSize 以兼容接口
    const res = await getPageAlarmIndicator(1, 9999);
    const records = res.data?.records || res.data || [];

    // 将数据映射为标签格式
    barriers.value = records.map((item: any, index: number) => {
      const colorConfig = alarmLevelColors[(item.alarmLevel - 1) % alarmLevelColors.length];
      return {
        id: item.alarmId,
        name: item.indicatorCode,
        gradient: colorConfig.gradient,
        textColor: colorConfig.textColor,
        alarmData: item
      };
    });

    // 默认选中第一个
    if (barriers.value.length > 0 && activeBarrier.value === null) {
      activeBarrier.value = barriers.value[0].id;
      lastBarrierId.value = activeBarrier.value;
    }
  } catch (error) {
    console.error('加载屏障失效排名数据失败:', error);
  }
};

// 当前选中的告警信息
const activeBarrierInfo = computed(() => {
  const displayId = activeBarrier.value ?? lastBarrierId.value;
  const barrier = barriers.value.find((b) => b.id === displayId);
  if (!barrier?.alarmData) return {name: '', startTime: '', duration: '', description: '', combo: '', reason: ''};

  const data = barrier.alarmData;
  // 将持续时间（秒）转换为可读格式
  const formatDuration = (seconds: number) => {
    if (seconds < 60) {
      return `${seconds}秒`;
    }
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    if (hours > 0) return `${hours}小时${minutes}分钟`;
    return `${minutes}分钟`;
  };

  return {
    name: data.indicatorCode,
    startTime: data.startTime,
    duration: formatDuration(data.duration),
    description: data.description,
    combo: data.combineAlarm || '无',
    reason: data.reason || ''
  };
});

// 当前选中标签的文字颜色
const activeBarrierTextColor = computed(() => {
  const displayId = activeBarrier.value ?? lastBarrierId.value;
  const barrier = barriers.value.find((b) => b.id === displayId);
  const alarmLevel = barrier?.alarmData?.alarmLevel;

  // 根据 alarmLevel 返回对应的颜色
  switch (alarmLevel) {
    case 2:
      return '#FFE566'; // 黄色
    case 4:
      return '#E0B3FF'; // 紫色
    case 5:
      return '#FFB366'; // 橙色
    default:
      return barrier?.textColor || '#30DAFE';
  }
});

// 根据 alarmLevel 动态返回圆形背景图
const activeCircleBg = computed(() => {
  const displayId = activeBarrier.value ?? lastBarrierId.value;
  const barrier = barriers.value.find((b) => b.id === displayId);
  const alarmLevel = barrier?.alarmData?.alarmLevel;

  switch (alarmLevel) {
    case 1:
      return circle1Bg;
    case 2:
      return alarmLevel2Bg; // 黄色
    case 3:
      return circle3Bg;
    case 4:
      return alarmLevel4Bg; // 紫色
    case 5:
      return alarmLevel5Bg; // 橙色
    default:
      return circleBg; // 默认使用原始的 circle.png
  }
});

// 已移除分页逻辑，改为横向滚动展示全部

// 每2秒自动切换到下一个预警信息
let autoSwitchTimer: any = null;

const startAutoSwitch = () => {
  // 清除之前的定时器
  if (autoSwitchTimer) {
    clearInterval(autoSwitchTimer);
  }

  autoSwitchTimer = setInterval(() => {
    // 如果是手动选择状态，不自动切换
    if (isManualSelection.value || barriers.value.length === 0) return;

    const currentIndex = barriers.value.findIndex((b) => b.id === activeBarrier.value);
    const nextIndex = (currentIndex + 1) % barriers.value.length;
    activeBarrier.value = barriers.value[nextIndex].id;
  }, 2000);
};

// 停止自动切换
const stopAutoSwitch = () => {
  if (autoSwitchTimer) {
    clearInterval(autoSwitchTimer);
    autoSwitchTimer = null;
  }
};

// 清除恢复定时器
const clearResumeTimer = () => {
  if (resumeTimer) {
    clearTimeout(resumeTimer);
    resumeTimer = null;
  }
};

// 启动10秒后自动恢复定时器
const startResumeTimer = () => {
  clearResumeTimer();
  resumeTimer = setTimeout(() => {
    isManualSelection.value = false;
    startAutoSwitch();
  }, 10000); // 10秒后恢复
};

// 手动选择报警
const handleBarrierClick = (barrierId: number) => {
  const barrier = barriers.value.find((b) => b.id === barrierId);
  if (!barrier) return;

  // 如果点击的是已选中的barrier，则取消选中
  if (selectedCode.value === barrier.name) {
    // 取消选中，但信息仍显示该标签
    lastBarrierId.value = barrierId;
    selectedCode.value = null;
    activeBarrier.value = null;
    // 回显设备下拉框，仅重新加载报警信息
    if (activeDevice.value !== null) {
      loadChainAlarm({equipmentId: activeDevice.value || undefined});
    }
    startAutoSwitch();
    return;
  }

  activeBarrier.value = barrierId;
  lastBarrierId.value = barrierId;
  // 点击后尽可能滚动到中间
  nextTick(() => scrollBarrierItemIntoCenter(barrierId));
  selectedCode.value = barrier.name;
  isManualSelection.value = true;
  stopAutoSwitch();
  clearResumeTimer(); // 清除恢复定时器

  // 加载对应设备位号的报警信息，并在渲染后聚焦到对应节点
  pendingFocusCode.value = barrier.name;
  loadChainAlarm({code: barrier.name});
};

// 点击事件区域（关键工艺报警统计）
const handleEventClick = (eventName: string) => {
  // 如果点击的是已选中的事件，则取消选中
  if (selectedCode.value === eventName) {
    selectedCode.value = null;
    // 回显设备下拉框，仅重新加载报警信息
    if (activeDevice.value !== null) {
      loadChainAlarm({equipmentId: activeDevice.value || undefined});
    }
    return;
  }

  selectedCode.value = eventName;
  // 加载对应设备位号的报警信息，并在渲染后聚焦到对应节点
  // 需求：从“关键工艺报警统计”联动到“预警分析”时不显示时间轴
  pendingFocusCode.value = eventName;
  loadChainAlarm({code: eventName, showTimeline: false});
};

// 监听barriers变化，启动自动切换
watch(
  barriers,
  (newBarriers) => {
    if (newBarriers.length > 0 && !isManualSelection.value) {
      startAutoSwitch();
    }
  },
  {immediate: true}
);

const deviceOptions = ref([] as any[]);
const activeDevice = ref(null);
// 设备下拉显示名称
const activeDeviceLabel = computed(() => {
  const id = activeDevice.value;
  if (id == null) return '--';
  const found = deviceOptions.value.find((d) => d.value === id);
  const deviceInfo = found?.label ?? '--';
  const nodeInfo = activeNodeContent1.value || '';
  return nodeInfo ? `${deviceInfo}-${nodeInfo}` : deviceInfo;
});

// 加载设备列表
const loadDeviceList = async () => {
  try {
    const res = await getEquipmentList();
    const data = res.data || [];
    deviceOptions.value = data.map((item: any) => ({
      label: item.sbmc === '甲醇制氢装置' ? '甲醇缓冲罐' : item.sbmc,
      value: item.id
    }));
    // 默认选中第一项
    if (deviceOptions.value.length > 0 && activeDevice.value === null) {
      activeDevice.value = deviceOptions.value[0].value;
      // 加载节点图谱和报警数据
      await loadNodeMap(activeDevice.value);
    }
  } catch (error) {
    console.error('加载设备列表失败:', error);
  }
};

// 原因分析弹窗
const showAnalysisDialog = ref(false);

// 点击原因分析按钮
const handleAnalysisClick = () => {
  showAnalysisDialog.value = true;
  isManualSelection.value = true;
  stopAutoSwitch();
  clearResumeTimer(); // 清除恢复定时器
};

// 监听弹窗关闭，恢复自动轮询
watch(showAnalysisDialog, (newVal) => {
  if (!newVal) {
    // 弹窗关闭时，恢复自动轮询
    isManualSelection.value = false;
    startAutoSwitch();
  }
});

// 处置建议弹窗
const showSuggestionDialog = ref(false);

// 点击处置建议按钮
const handleSuggestionClick = () => {
  showSuggestionDialog.value = true;
  isManualSelection.value = true;
  stopAutoSwitch();
  clearResumeTimer(); // 清除恢复定时器
};

// 监听处置建议弹窗关闭，恢复自动轮询
watch(showSuggestionDialog, (newVal) => {
  if (!newVal) {
    // 弹窗关闭时，恢复自动轮询
    isManualSelection.value = false;
    startAutoSwitch();
  }
});

const logicFlowRef = ref(null);
const timelineRef = ref(null);
const kamBottomRef = ref(null);
let lf: LogicFlow | null = null;

/**
 * 根据位号聚焦到对应节点（将节点平移至视图中心）
 */
function focusNodeByIndicatorCode(code: string) {
  if (!lf || !logicFlowRef.value) return;
  try {
    const raw = (lf as any).getGraphRawData?.();
    const nodes = Array.isArray(raw?.nodes) ? raw!.nodes : [];

    // 先通过当前报警数据找到对应的 nodeId（数值）
    let targetNodeIdNum: number | null = null;
    for (const item of currentAlarmData.value || []) {
      const ic = item?.riskAlarm?.indicatorCode;
      if (ic && String(ic) === String(code)) {
        targetNodeIdNum = Number(item.nodeId);
        break;
      }
    }

    // 回退：直接从节点 properties.label.alarms 中匹配位号
    let targetLfId: string | null = null;
    for (const n of nodes) {
      const label: any = (n as any)?.properties?.label;
      if (!label) continue;
      if (targetNodeIdNum !== null) {
        if (Number(label.nodeId) === targetNodeIdNum) {
          targetLfId = (n as any).id;
          break;
        }
      } else {
        const alarms: any[] = Array.isArray(label.alarms) ? label.alarms : [];
        if (alarms.some((a) => a && String(a.indicatorCode || '') === String(code))) {
          targetLfId = (n as any).id;
          break;
        }
      }
    }

    if (!targetLfId) return;

    const model: any = (lf as any).getNodeModelById?.(targetLfId);
    if (!model) return;

    const x = Number(model.x);
    const y = Number(model.y);

    const {TRANSLATE_X, TRANSLATE_Y, SCALE_X} = (lf as any).getTransform();
    const scale = Number(SCALE_X || 1);
    const cw = logicFlowRef.value.clientWidth || 0;
    const ch = logicFlowRef.value.clientHeight || 0;

    // 目标居中后的平移量（绝对值），转换为相对位移传给 translate
    const newTX = cw / 2 - x * scale;
    const newTY = ch / 2 - y * scale;
    const dx = newTX - Number(TRANSLATE_X || 0);
    const dy = newTY - Number(TRANSLATE_Y || 0);

    // 平移视角到中心
    (lf as any).translate(dx, dy);
  } catch {
    // 忽略定位异常，避免影响主流程
  }
}

// 全屏状态管理
const isFullscreen = ref(false);
const isExitingFullscreen = ref(false);

// 切换全屏
const toggleFullscreen = () => {
  if (isFullscreen.value) {
    // 退出全屏
    isExitingFullscreen.value = true;
    setTimeout(() => {
      isFullscreen.value = false;
      isExitingFullscreen.value = false;
    }, 500); // 动画时长与 animate.css 的 zoomOut 一致
  } else {
    // 进入全屏
    isFullscreen.value = true;
    // 下一帧等待容器尺寸稳定后自适应缩放以适配所有节点
    nextTick(() => {
      setTimeout(() => {
        try {
          fitAllToView();
        } catch {
        }
      }, 300);
    });
  }
};

const timelinePercent = ref(0); // 时间轴从起点开始
const timelineMarks = reactive([]);
const timelineHoverPercent = ref(0); // hover位置百分比
const showTimelineHover = ref(false); // 是否显示hover提示
// 轴线位置（稍微靠下）与尺寸配置
const axisTopStyle = '56%';
const tickHeight = 10; // 刻度线高度（px）
const handleLineHeight = 38; // 手柄竖线总高度（px），以轴线为中点
const tickList = computed(() => {
  const len = timelineMarks.length;
  if (len <= 1) return [{pos: 0, label: timelineMarks[0] ?? ''}];
  return timelineMarks.map((label, i) => ({
    label,
    pos: (i / (len - 1)) * 100
  }));
});

// 当前时间精确显示（年月日时分秒）
const currentTimelineTime = computed(() => {
  const currentTime = getCurrentTimeFromPercent(timelinePercent.value);
  if (!currentTime) return '无数据';

  // 格式化为年月日时分秒
  return currentTime.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
});

// hover时显示的时间
const hoverTimelineTime = computed(() => {
  const hoverTime = getCurrentTimeFromPercent(timelineHoverPercent.value);
  if (!hoverTime) return '无数据';

  // 格式化为年月日时分秒
  return hoverTime.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
});

let dragging = false;

// 限制 LogicFlow 在拖动时的刷新频率，避免卡顿
let lfUpdateTimer: number | null = null;
let lastLFUpdateTs = 0;
const MIN_LF_UPDATE_INTERVAL = 120; // 毫秒，拖动时限制刷新频率（约 8fps）

function scheduleLFUpdate(immediate = false) {
  if (!lf) return;
  const now = Date.now();
  const doRender = () => {
    if (lfUpdateTimer) {
      clearTimeout(lfUpdateTimer);
      lfUpdateTimer = null;
    }
    lastLFUpdateTs = Date.now();
    renderLogicFlow(false);
  };

  if (immediate) {
    doRender();
    return;
  }

  const elapsed = now - lastLFUpdateTs;
  if (elapsed >= MIN_LF_UPDATE_INTERVAL) {
    doRender();
  } else if (lfUpdateTimer === null) {
    lfUpdateTimer = window.setTimeout(doRender, MIN_LF_UPDATE_INTERVAL - elapsed);
  }
}

function startDragging(event: MouseEvent) {
  dragging = true;
  document.addEventListener('mousemove', handleDragging);
  document.addEventListener('mouseup', stopDragging);
  event.preventDefault();
}

function handleDragging(event: MouseEvent) {
  if (!dragging || !timelineRef.value) return;
  const rect = timelineRef.value.getBoundingClientRect();
  const percent = ((event.clientX - rect.left) / rect.width) * 100;
  timelinePercent.value = Math.min(100, Math.max(0, percent));
  // 拖动过程中，对图谱渲染进行节流，提升跟随流畅度
  scheduleLFUpdate(false);
}

function stopDragging() {
  dragging = false;
  document.removeEventListener('mousemove', handleDragging);
  document.removeEventListener('mouseup', stopDragging);
  // 拖动结束后，立即做一次最终刷新
  scheduleLFUpdate(true);
}

// 点击时间轴定位
function handleTimelineClick(event: MouseEvent) {
  if (!timelineRef.value) return;
  const rect = timelineRef.value.getBoundingClientRect();
  const percent = ((event.clientX - rect.left) / rect.width) * 100;
  timelinePercent.value = Math.min(100, Math.max(0, percent));
  // 点击定位，立即刷新
  scheduleLFUpdate(true);
}

// 鼠标移动到时间轴时显示hover时间
function handleTimelineMouseMove(event: MouseEvent) {
  if (!timelineRef.value) return;
  const rect = timelineRef.value.getBoundingClientRect();
  const percent = ((event.clientX - rect.left) / rect.width) * 100;
  timelineHoverPercent.value = Math.min(100, Math.max(0, percent));
}

// 鼠标进入时间轴
function handleTimelineMouseEnter() {
  showTimelineHover.value = true;
}

// 鼠标离开时间轴
function handleTimelineMouseLeave() {
  showTimelineHover.value = false;
}

// 定义节点数据格式（不再使用树形结构）
type FlowNodeData = {
  nodeId: number;
  nodeName: string;
  code?: string;
  content1?: string;
  content2?: string;
  alarms?: any[]; // 报警信息列表
};

// 定义图数据结构（节点 + 边）
type FlowGraphData = {
  nodes: FlowNodeData[];
  relations: { nodeFrom: number; nodeTo: number }[];
};

// 当前设备的节点图和报警数据
const currentGraphData = ref(null) as any;
const currentAlarmData = ref([]) as any;
const allAlarmTimes = ref([]) as any;

// 偏差（节点）选择与选项
const activeDeviation = ref(null);
const deviationOptions = ref([] as any[]);

// 选中的设备位号和显示状态
const selectedCode = ref(null);
const showTimeline = ref(false);
// 报警详情弹窗
const showAlarmDialog = ref(false);
const activeAlarm = ref(null);
// 当前点击节点的 content1 信息
const activeNodeContent1 = ref('');

// 计算报警结束时间（startTime + duration[秒]）
const activeAlarmEndTime = computed(() => {
  const a = activeAlarm.value;
  if (!a?.startTime) return '--';
  const start = dayjs(a.startTime);
  const durationSec = Number(a.duration || 0);
  if (Number.isNaN(durationSec)) return '--';
  const end = start.add(durationSec, 'second');
  return end.format('YYYY-MM-DD HH:mm:ss');
});

/**
 * 从一组报警记录中选取最新的一条（按 startTime 最大）
 * - 过滤无效项与无 startTime 的项
 * - 相同时返回任意一条（稳定性足够）
 */
function getLatestAlarmFromList(list: any[]): any | null {
  if (!Array.isArray(list) || list.length === 0) return null;
  const valid = list
    .filter((a) => a && a.startTime)
    .map((a) => ({raw: a, ts: new Date(a.startTime).getTime()}))
    .filter((a) => Number.isFinite(a.ts));
  if (valid.length === 0) return null;
  valid.sort((a, b) => b.ts - a.ts);
  return valid[0].raw;
}

/**
 * 将报警信息挂载到节点数据上
 */
const mountAlarmsToGraph = (graphData: any, alarmMap: any) => {
  graphData.nodes.forEach((node) => {
    node.alarms = alarmMap.get(node.nodeId) || [];
  });
};

/**
 * 基于当前图数据更新偏差下拉选项
 * 规则：排除第一列（无入边）与最后一列（无出边）的节点
 */
function updateDeviationOptions() {
  deviationOptions.value = [];
  const graph = currentGraphData.value;
  if (!graph || !graph.nodes?.length) return;

  // 统计入度/出度，用于排除首列/末列
  const inDeg = new Map();
  const outDeg = new Map();
  graph.nodes.forEach((n) => {
    inDeg.set(n.nodeId, 0);
    outDeg.set(n.nodeId, 0);
  });
  graph.relations.forEach((rel) => {
    inDeg.set(rel.nodeTo, (inDeg.get(rel.nodeTo) || 0) + 1);
    outDeg.set(rel.nodeFrom, (outDeg.get(rel.nodeFrom) || 0) + 1);
  });

  // 仅保留：非首尾列 且 当前存在报警的节点（node.alarms 中存在有效报警）
  const opts = graph.nodes
    .filter((n) => (inDeg.get(n.nodeId) || 0) > 0 && (outDeg.get(n.nodeId) || 0) > 0)
    .filter((n) => Array.isArray(n.alarms) && n.alarms.some((a) => !!a))
    .map((n) => ({label: n.nodeName || String(n.nodeId), value: n.nodeId}));

  deviationOptions.value = opts;

  // 选中逻辑：若当前值不在列表中或为空，则默认选中第一项
  const exists = activeDeviation.value != null && opts.some((o) => o.value === activeDeviation.value);
  if (!exists) {
    activeDeviation.value = opts.length > 0 ? opts[0].value : null;
  }
}

/**
 * 加载节点图谱（需要传入设备id）
 */
const loadNodeMap = async (equipmentId: number) => {
  try {
    const nodeRes = await getNodeMap(equipmentId);

    // 获取节点和关系数据
    const nodeMapData = nodeRes.data || {nodes: [], relations: []};
    currentGraphData.value = {
      nodes: (nodeMapData.nodes || []).map((n: any) => ({
        ...n,
        alarms: []
      })),
      relations: nodeMapData.relations || []
    };

    // 更新偏差下拉选项
    updateDeviationOptions();

    // 加载当前设备的报警数据：首次进入默认不显示时间轴
    await loadChainAlarm({equipmentId, code: selectedCode.value, showTimeline: false});
  } catch (error) {
    console.error('加载节点图谱失败:', error);
  }
};

/**
 * 加载报警链路数据
 * @param params - { equipmentId?: number, code?: string }
 */
const loadChainAlarm = async (params: { equipmentId?: number; code?: string; showTimeline?: boolean }) => {
  try {
    const alarmRes = await getChainAlarm(params.equipmentId, params.code);
    const alarmData = alarmRes.data || [];

    // 不过滤数据，所有返回的节点都标记为报警状态
    currentAlarmData.value = alarmData;

    // 创建 nodeId -> alarms 的映射
    // 所有返回的 nodeId 都应该被标记为报警节点，即使 riskAlarm 为 null
    const alarmMap = new Map();
    alarmData.forEach((item: any) => {
      const nodeId = item.nodeId;
      if (!alarmMap.has(nodeId)) {
        alarmMap.set(nodeId, []);
      }
      // 即使 riskAlarm 为 null 也添加，表示该节点在报警链路中
      alarmMap.get(nodeId)!.push(item.riskAlarm);
    });

    // 清空之前的报警信息
    if (currentGraphData.value) {
      currentGraphData.value.nodes.forEach((node) => {
        node.alarms = [];
      });
    }

    // 将报警信息挂载到节点上
    if (currentGraphData.value) {
      mountAlarmsToGraph(currentGraphData.value, alarmMap);
    }
    // 报警信息挂载后，刷新“偏差”下拉：仅保留有报警的节点
    updateDeviationOptions();

    // 是否展示时间轴：默认展示；若明确传入 showTimeline=false，则隐藏
    showTimeline.value = params.showTimeline !== false && Array.isArray(alarmData) && alarmData.length > 0;
    // 仅用有效报警生成时间轴刻度；若均无有效报警，将显示“无数据”刻度
    const validAlarms = alarmData.filter((item: any) => item?.riskAlarm);
    generateTimelineMarks(validAlarms);

    // 渲染流程图
    // 若存在待聚焦位号，则不立即重置视图，避免与后续定位冲突
    const shouldReset = !pendingFocusCode.value;
    renderLogicFlow(shouldReset);

    // 若存在待聚焦位号，则在渲染完成后执行定位
    if (pendingFocusCode.value) {
      const codeToFocus = pendingFocusCode.value;
      // 轻微延迟，确保 DOM 与内部模型已就绪
      setTimeout(() => {
        try {
          focusNodeByIndicatorCode(codeToFocus);
        } finally {
          pendingFocusCode.value = null;
        }
      }, 50);
    }
  } catch (error) {
    console.error('加载报警链路数据失败:', error);
  }
};

/**
 * 生成时间轴刻度
 * 根据所有报警的开始时间和结束时间（开始时间 + 持续时间）生成时间轴刻度
 * 时间轴范围：最早报警开始时间 ～ 最晚报警结束时间
 * 刻度单位根据时间范围自动调整：秒/分钟/小时/天
 */
const generateTimelineMarks = (alarmData: any[]) => {
  if (!alarmData || alarmData.length === 0) {
    timelineMarks.splice(0, timelineMarks.length, '无数据');
    allAlarmTimes.value = [];
    return;
  }

  // 收集所有报警的时间信息（开始时间 + 结束时间）
  const times: { startTime: Date; endTime: Date }[] = [];
  alarmData.forEach((item: any) => {
    if (item.riskAlarm?.startTime) {
      const start = new Date(item.riskAlarm.startTime);
      const duration = item.riskAlarm.duration || 0; // 持续时间，单位：秒
      const end = new Date(start.getTime() + duration * 1000);
      times.push({startTime: start, endTime: end});
    }
  });

  allAlarmTimes.value = times;

  if (times.length === 0) {
    timelineMarks.splice(0, timelineMarks.length, '无数据');
    return;
  }

  // 找出最早的报警开始时间和最晚的报警结束时间
  const minTime = new Date(Math.min(...times.map((t) => t.startTime.getTime())));
  const maxTime = new Date(Math.max(...times.map((t) => t.endTime.getTime())));

  // 计算时间范围
  const timeRangeMs = maxTime.getTime() - minTime.getTime();
  const timeRangeSeconds = timeRangeMs / 1000;

  // 计算时间轴实际宽度（减去左右边距），每0.5cm一个刻度
  // 假设时间轴容器宽度约为视口宽度 - 边距，1cm ≈ 37.8px (96 DPI)
  const timelineWidth = timelineRef.value?.clientWidth || 800; // 默认800px
  const cmToPx = 18.9; // 0.5cm ≈ 18.9px，提高刻度密度
  const tickCount = Math.max(6, Math.min(Math.floor(timelineWidth / cmToPx), 30)); // 最少6个，最多30个

  // 根据时间范围选择刻度格式
  let formatOptions: Intl.DateTimeFormatOptions;
  if (timeRangeSeconds < 60) {
    formatOptions = {hour: '2-digit', minute: '2-digit', second: '2-digit'};
  } else if (timeRangeSeconds < 7200) {
    formatOptions = {hour: '2-digit', minute: '2-digit'};
  } else if (timeRangeSeconds < 259200) {
    formatOptions = {month: '2-digit', day: '2-digit', hour: '2-digit'};
  } else {
    formatOptions = {month: '2-digit', day: '2-digit'};
  }

  const marks: string[] = [];
  for (let i = 0; i < tickCount; i++) {
    const time = new Date(minTime.getTime() + (timeRangeMs * i) / (tickCount - 1));
    marks.push(time.toLocaleString('zh-CN', formatOptions));
  }

  timelineMarks.splice(0, timelineMarks.length, ...marks);
};

// 将时间转换为时间轴上的百分比（用于标注箭头位置）
function getPercentOfTime(time: Date): number | null {
  if (allAlarmTimes.value.length === 0) return null;
  const min = Math.min(...allAlarmTimes.value.map((t) => t.startTime.getTime()));
  const max = Math.max(...allAlarmTimes.value.map((t) => t.endTime.getTime()));
  if (max <= min) return 0;
  const ms = time.getTime();
  const clamped = Math.min(Math.max(ms, min), max);
  return ((clamped - min) / (max - min)) * 100;
}

// 偏差报警开始时间标注（支持同一时间多条，自动水平错位避让）
const deviationMarkers = computed(() => {
  const nodeId = activeDeviation.value;
  const graph = currentGraphData.value;
  if (nodeId == null || !graph) return [];
  const node = graph.nodes.find((n) => n.nodeId === nodeId);
  if (!node || !node.alarms || node.alarms.length === 0) return [];

  // 1) 收集所有开始时间对应的百分比（0-100）
  const rawPercents: number[] = [];
  node.alarms.forEach((alarm: any) => {
    if (alarm && alarm.startTime) {
      const p = getPercentOfTime(new Date(alarm.startTime));
      if (p != null) rawPercents.push(p);
    }
  });
  if (rawPercents.length === 0) return [];

  // 2) 以千分位归组，视为“同一位置”的一组（避免浮点误差导致看似接近却分组失败）
  const groups = new Map();
  rawPercents.forEach((v) => {
    const key = Math.round(v * 1000); // 千分位
    const list = groups.get(key) || [];
    list.push(v);
    groups.set(key, list);
  });

  // 3) 为每组生成水平像素错位，靠近边界则单侧偏移
  const markers: { left: number; offset: number }[] = [];
  const spacing = 10; // 单个标注之间的水平间距（px）
  const width = timelineRef.value?.clientWidth || 800;

  groups.forEach((values, key) => {
    // 组的中心百分比（使用首个值）
    const center = values[0];
    // 离左右边界的可用像素宽度（按时间轴真实像素计算）
    const centerPx = (center / 100) * width;
    const leftAvail = centerPx - 0;
    const rightAvail = width - centerPx;

    const count = values.length;
    const offsets: number[] = [];

    const needHalfSpan = ((count - 1) / 2) * spacing;
    if (leftAvail < needHalfSpan + 1) {
      // 靠左边界：仅向右偏移
      for (let i = 0; i < count; i++) offsets.push(i * spacing);
    } else if (rightAvail < needHalfSpan + 1) {
      // 靠右边界：仅向左偏移
      for (let i = 0; i < count; i++) offsets.push(-i * spacing);
    } else {
      // 居中对称：使整体居中在 center 上
      for (let i = 0; i < count; i++) offsets.push((i - (count - 1) / 2) * spacing);
    }

    // 输出 markers（按 values 次序生成）
    values.forEach((_v, idx) => {
      markers.push({left: center, offset: offsets[idx]});
    });
  });

  // 保持标注从左到右的顺序，便于阅读
  markers.sort((a, b) => (a.left === b.left ? a.offset - b.offset : a.left - b.left));
  return markers;
});

// 监听设备切换：仅刷新报警信息（不再重新获取节点信息）
watch(activeDevice, (newDevice, oldDevice) => {
  if (newDevice !== null && newDevice !== oldDevice) {
    loadChainAlarm({equipmentId: newDevice, code: selectedCode.value || undefined});
  }
});

/**
 * 根据当前时间获取节点的报警状态
 * 如果节点在当前时间有激活的报警，返回该报警信息
 * 支持一个节点有多个报警，返回第一个在当前时间范围内的报警
 */
const getNodeAlarmAtTime = (node: FlowNodeData, currentTime: Date) => {
  if (!node.alarms || node.alarms.length === 0) return null;

  // 找出在当前时间激活的报警（报警时间范围：startTime ～ startTime + duration）
  for (const alarm of node.alarms) {
    if (!alarm || !alarm.startTime) continue;
    const startTime = new Date(alarm.startTime);
    const duration = alarm.duration || 0; // 持续时间，单位：秒
    const endTime = new Date(startTime.getTime() + duration * 1000);

    if (currentTime >= startTime && currentTime <= endTime) {
      return alarm;
    }
  }
  return null;
};

/**
 * 根据时间轴百分比获取当前时间
 * 将时间轴的拖动位置（0-100%）映射到实际时间范围
 */
const getCurrentTimeFromPercent = (percent: number): Date | null => {
  if (allAlarmTimes.value.length === 0) return null;

  const times = allAlarmTimes.value;
  const minTime = Math.min(...times.map((t) => t.startTime.getTime()));
  const maxTime = Math.max(...times.map((t) => t.endTime.getTime()));
  const timeRange = maxTime - minTime;

  return new Date(minTime + (timeRange * percent) / 100);
};

// LogicFlow 节点/连线 的算法（基于图结构的分层布局）
type FlowLayoutOptions = {
  hGap?: number; // 层间距
  vGap?: number; // 同层节点间距
  left?: number;
  top?: number;
};

/**
 * 从图数据构建流程图（使用分层布局算法）
 * 算法思路：
 * 1. 计算每个节点的层级（基于拓扑排序）
 * 2. 按层级分组节点
 * 3. 在每层中均匀分布节点
 * 4. 根据 relations 创建边
 */
function buildFlowFromGraph(graphData?: FlowGraphData, opts: FlowLayoutOptions = {}) {
  if (!graphData || !graphData.nodes || graphData.nodes.length === 0) {
    return {nodes: [] as any[], edges: [] as EdgeConfig[]};
  }

  const hGap = opts.hGap ?? 240; // 层间距
  const vGap = opts.vGap ?? 160; // 同层节点间距
  const left = opts.left ?? 120;
  const top = opts.top ?? 0;

  const {nodes: graphNodes, relations} = graphData;

  // 1. 计算每个节点的入度
  const inDegree = new Map();
  const outEdges = new Map(); // nodeId -> [target nodeIds]

  graphNodes.forEach((node) => {
    inDegree.set(node.nodeId, 0);
    outEdges.set(node.nodeId, []);
  });

  relations.forEach((rel) => {
    inDegree.set(rel.nodeTo, (inDegree.get(rel.nodeTo) || 0) + 1);
    const targets = outEdges.get(rel.nodeFrom) || [];
    targets.push(rel.nodeTo);
    outEdges.set(rel.nodeFrom, targets);
  });

  // 2. 使用拓扑排序计算每个节点的层级
  const nodeLevel = new Map();
  const queue: number[] = [];

  // 找出所有入度为 0 的节点（根节点）
  graphNodes.forEach((node) => {
    if (inDegree.get(node.nodeId) === 0) {
      queue.push(node.nodeId);
      nodeLevel.set(node.nodeId, 0);
    }
  });

  // BFS 计算层级
  while (queue.length > 0) {
    const nodeId = queue.shift()!;
    const level = nodeLevel.get(nodeId)!;

    const targets = outEdges.get(nodeId) || [];
    targets.forEach((targetId) => {
      const currentLevel = nodeLevel.get(targetId) || 0;
      nodeLevel.set(targetId, Math.max(currentLevel, level + 1));

      const degree = inDegree.get(targetId)! - 1;
      inDegree.set(targetId, degree);

      if (degree === 0) {
        queue.push(targetId);
      }
    });
  }

  // 处理未分配层级的节点（可能存在环）
  graphNodes.forEach((node) => {
    if (!nodeLevel.has(node.nodeId)) {
      nodeLevel.set(node.nodeId, 0);
    }
  });

  // 3. 按层级分组节点
  const levelGroups = new Map();
  graphNodes.forEach((node) => {
    const level = nodeLevel.get(node.nodeId)!;
    if (!levelGroups.has(level)) {
      levelGroups.set(level, []);
    }
    levelGroups.get(level)!.push(node);
  });

  // 4. 构建入边映射（nodeId -> parent nodeIds）
  const inEdges = new Map();
  graphNodes.forEach((node) => {
    inEdges.set(node.nodeId, []);
  });
  relations.forEach((rel) => {
    const parents = inEdges.get(rel.nodeTo) || [];
    parents.push(rel.nodeFrom);
    inEdges.set(rel.nodeTo, parents);
  });

  // 5. 为每个节点分配坐标（多轮处理，优化垂直位置）
  const nodePositions = new Map();
  const nodeIdMap = new Map(); // nodeId -> LogicFlow id

  let lfNodeId = 1;

  // 第一轮：初始化坐标
  levelGroups.forEach((nodesInLevel, level) => {
    const x = left + level * hGap;
    const totalHeight = (nodesInLevel.length - 1) * vGap;
    const startY = top - totalHeight / 2;

    nodesInLevel.forEach((node, index) => {
      const y = startY + index * vGap;
      nodePositions.set(node.nodeId, {x, y});
      nodeIdMap.set(node.nodeId, `n${lfNodeId++}`);
    });
  });

  // 第二轮：根据父节点位置调整子节点的垂直位置（从第1层开始，第0层是根节点）
  const maxLevel = Math.max(...Array.from(levelGroups.keys()));
  for (let level = 1; level <= maxLevel; level++) {
    const nodesInLevel = levelGroups.get(level) || [];

    nodesInLevel.forEach((node) => {
      const parentIds = inEdges.get(node.nodeId) || [];
      if (parentIds.length > 0) {
        // 计算所有父节点的平均 Y 坐标
        const parentYPositions = parentIds.map((pid) => nodePositions.get(pid)?.y).filter((y): y is number => y !== undefined);

        if (parentYPositions.length > 0) {
          const avgY = parentYPositions.reduce((sum, y) => sum + y, 0) / parentYPositions.length;
          const currentPos = nodePositions.get(node.nodeId)!;
          nodePositions.set(node.nodeId, {x: currentPos.x, y: avgY});
        }
      }
    });
  }

  // 第三轮：修正同层节点的重叠（确保最小间距为vGap）
  for (let level = 0; level <= maxLevel; level++) {
    const nodesInLevel = levelGroups.get(level) || [];
    if (nodesInLevel.length <= 1) continue;

    // 按Y坐标排序
    const sortedNodes = [...nodesInLevel].sort((a, b) => {
      const posA = nodePositions.get(a.nodeId)!;
      const posB = nodePositions.get(b.nodeId)!;
      return posA.y - posB.y;
    });

    // 检测并修正重叠
    for (let i = 1; i < sortedNodes.length; i++) {
      const prevNode = sortedNodes[i - 1];
      const currNode = sortedNodes[i];
      const prevPos = nodePositions.get(prevNode.nodeId)!;
      const currPos = nodePositions.get(currNode.nodeId)!;

      const distance = currPos.y - prevPos.y;
      if (distance < vGap) {
        // 向下推移当前节点
        const newY = prevPos.y + vGap;
        nodePositions.set(currNode.nodeId, {x: currPos.x, y: newY});

        // 递归向下推移后续节点
        for (let j = i + 1; j < sortedNodes.length; j++) {
          const nextNode = sortedNodes[j];
          const nextPos = nodePositions.get(nextNode.nodeId)!;
          const prevNextPos = nodePositions.get(sortedNodes[j - 1].nodeId)!;
          if (nextPos.y - prevNextPos.y < vGap) {
            nodePositions.set(nextNode.nodeId, {x: nextPos.x, y: prevNextPos.y + vGap});
          }
        }
      }
    }
  }

  // 5. 生成 LogicFlow 节点和边
  const lfNodes: any[] = [];
  const lfEdges: EdgeConfig[] = [];

  graphNodes.forEach((node) => {
    const pos = nodePositions.get(node.nodeId)!;
    const id = nodeIdMap.get(node.nodeId)!;
    const level = nodeLevel.get(node.nodeId) || 0;
    lfNodes.push({
      id,
      type: 'flow-node',
      x: pos.x,
      y: pos.y,
      text: {value: {...node, level, maxLevel}}
    });
  });

  let edgeId = 1;
  relations.forEach((rel) => {
    const sourceId = nodeIdMap.get(rel.nodeFrom);
    const targetId = nodeIdMap.get(rel.nodeTo);
    if (sourceId && targetId) {
      lfEdges.push({
        id: `e${edgeId++}`,
        type: 'flow-edge',
        sourceNodeId: sourceId,
        targetNodeId: targetId
      });
    }
  });

  return {nodes: lfNodes, edges: lfEdges};
}

class FlowNodeView extends HtmlNode {
  setHtml(rootEl: SVGForeignObjectElement) {
    const modelAny: any = this.props.model as any;
    const raw = (modelAny?.properties?.label ?? {}) as any;

    // 获取报警信息：如果有时间轴则根据时间获取，否则直接取第一个报警
    let isWarn = false;
    let alarmInfo: any = null;

    if (showTimeline.value) {
      // 有时间轴时，根据时间轴位置获取报警
      const currentTime = getCurrentTimeFromPercent(timelinePercent.value);
      if (currentTime && raw) {
        alarmInfo = getNodeAlarmAtTime(raw, currentTime);
        isWarn = alarmInfo !== null;
      }
    } else {
      // 无时间轴时，展示该节点最新的报警（按 startTime 最大）
      if (Array.isArray(raw?.alarms) && raw.alarms.length > 0) {
        isWarn = true; // 在报警链路中依然标记为报警状态
        const latest = getLatestAlarmFromList(raw.alarms);
        alarmInfo = latest;
      }
    }

    const escapeHtml = (s: unknown) =>
      String(s ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');

    // 级别映射到颜色：3重大→红，2较大→橙，1一般→黄，0低→蓝
    const mapLevelToColor = (level: number): { bgColor: string; borderColor: string; textColor: string } => {
      switch (level) {
        case 3: // 重大 - 红
          return {
            bgColor: 'rgba(149, 29, 28, 0.2)',
            borderColor: 'rgba(149, 29, 28, 0.5)',
            textColor: '#951d1c'
          };
        case 2: // 较大 - 橙
          return {
            bgColor: 'rgba(255, 140, 0, 0.2)',
            borderColor: 'rgba(255, 140, 0, 0.5)',
            textColor: '#ff8c00'
          };
        case 1: // 一般 - 黄
          return {
            bgColor: 'rgba(255, 242, 153, 0.2)',
            borderColor: 'rgba(255, 242, 153, 0.5)',
            textColor: '#fff299'
          };
        case 0: // 低 - 蓝
        default:
          return {
            bgColor: 'rgba(15, 64, 245, 0.2)',
            borderColor: 'rgba(15, 64, 245, 0.5)',
            textColor: '#0f40f5'
          };
      }
    };

    const nodeName = String(raw?.nodeName ?? '--');
    const level = raw?.level ?? 0;
    const maxLevel = raw?.maxLevel ?? level;
    const nodeType = raw?.nodeType ?? 0;

    // 根据nodeType判断是否显示bars：nodeType为1、4、5时不显示
    // baseValues：长度为4的字符串数组，对应四个bars的阈值；根据指示器所在bar索引（1-4）动态取 baseValues[index-1]
    const baseValues: string[] = Array.isArray(raw?.baseValues) ? (raw.baseValues as string[]) : [];
    let barsHtml = '';
    if (nodeType !== 1 && nodeType !== 4 && nodeType !== 5) {
      const alarmLevel = alarmInfo?.alarmLevel || 0;
      // 根据 alarmLevel 高亮对应的 bar
      const getBarClass = (barIndex: number) => {
        return alarmLevel === barIndex ? 'lf-flow-node__bar--active' : '';
      };

      // 生成指示器（报警节点显示在对应报警等级的bar上，非报警节点默认显示在bar-1上）
      const indicatorLevel = alarmLevel > 0 ? alarmLevel : 1;
      const barText = String(baseValues[indicatorLevel - 1] ?? '');
      const indicatorSvg = `
        <svg class="lf-flow-node__bar-indicator bar-indicator-${indicatorLevel}" viewBox="0 0 12 8" xmlns="http://www.w3.org/2000/svg">
          <path d="M6 8L0 0H12L6 8Z" fill="currentColor"/>
        </svg>
      `;
      barsHtml = `
      <div class=\"lf-flow-node__bars-wrapper\">
        ${barText ? `<div class=\"lf-flow-node__bar-value bar-value-${indicatorLevel}\">\n        ${escapeHtml(barText)}</div>` : ''}
        ${indicatorSvg}
        <div class=\"lf-flow-node__bars\">
          <i class=\"lf-flow-node__bar bar-1 ${getBarClass(1)}\"></i>
          <i class=\"lf-flow-node__bar bar-2 ${getBarClass(2)}\"></i>
          <i class=\"lf-flow-node__bar bar-3 ${getBarClass(3)}\"></i>
          <i class=\"lf-flow-node__bar bar-4 ${getBarClass(4)}\"></i>
        </div>
      </div>
    `;
    }

    // 构建节点内容（第一行：标题/节点名称）
    let contentHtml = `<div class=\"lf-flow-node__title\">${escapeHtml(nodeName)}</div>`;

    // 当nodeType为5时，显示最大初始风险和最大残余风险
    if (nodeType === 5) {
      const maxInitValue = String(raw?.maxInitValue ?? '');
      const maxCanyuValue = String(raw?.maxCanyuValue ?? '');
      const maxInitLevel = raw?.maxInitLevel ?? 0;
      const maxCanyuLevel = raw?.maxCanyuLevel ?? 0;

      if (maxInitValue) {
        const initColors = mapLevelToColor(maxInitLevel);
        contentHtml += `<div class=\"lf-flow-node__risk-info\">最大初始风险: <span class=\"risk-value\" style=\"background: ${initColors.bgColor}; border-color: ${initColors.borderColor}; color: ${initColors.textColor};\">${escapeHtml(maxInitValue)}</span></div>`;
      }
      if (maxCanyuValue) {
        const canyuColors = mapLevelToColor(maxCanyuLevel);
        contentHtml += `<div class=\"lf-flow-node__risk-info\">最大残余风险: <span class=\"risk-value\" style=\"background: ${canyuColors.bgColor}; border-color: ${canyuColors.borderColor}; color: ${canyuColors.textColor};\">${escapeHtml(maxCanyuValue)}</span></div>`;
      }
    }

    // 第二行：报警位号来自 nodeMap 的 code；右侧可选显示报警值（若存在）
    const codeStr = String(raw?.code ?? '');
    const alarmValueStr = String(alarmInfo?.alarmValue ?? '');
    if (codeStr || alarmValueStr) {
      contentHtml += `<div class=\"lf-flow-node__indicator-wrapper\">`;
      // 无 bars 时不展示阈值（baseValues 仅用于 bars 阶段阈值显示）
      // 指示器（报警位号和报警值）
      if (codeStr || alarmValueStr) {
        contentHtml += `<div class=\"lf-flow-node__indicator\">\n        ${codeStr ? `<span class=\\\"indicator-code\\\">${escapeHtml(codeStr)}</span>` : ''}\n        ${alarmValueStr ? `<span class=\\\"indicator-value\\\">${escapeHtml(alarmValueStr)}</span>` : ''}\n      </div>`;
      }

      contentHtml += `</div>`;
    }
    // 第三行：between-around 布局，分别展示 content1 与 content2（无报警也显示）
    const c1 = String(raw?.content1 ?? '');
    const c2 = String(raw?.content2 ?? '');
    if (c1 || c2) {
      contentHtml += `<div class=\"lf-flow-node__meta\">\n        ${c1 ? `<span>${escapeHtml(c1)}</span>` : ''}\n        ${c2 ? `<span>${escapeHtml(c2)}</span>` : ''}\n      </div>`;
    }

    // 直接通过 innerHTML 设置整个节点 HTML
    // 第一列使用 flow-border-reason.png 背景图
    let nodeClass = level === 0 ? 'lf-flow-node--reason' : isWarn ? 'lf-flow-node--warn' : '';
    // type为4或5的节点添加特殊类，用于设置右内边距为0
    if (nodeType === 4 || nodeType === 5) {
      nodeClass += ' lf-flow-node--no-right-padding';
    }
    const contentWrapperClass = level === 0 || nodeType === 4 || nodeType === 5 ? 'lf-flow-node__content-wrapper--center' : '';
    rootEl.innerHTML = `
    <div class="lf-flow-node ${nodeClass}" style="width:100%;height:100%;display:flex;flex-direction:column;">
      <div class="${contentWrapperClass}" style="flex:1;">${contentHtml}</div>
      ${barsHtml}
    </div>
    `;

    // 后果节点（最后一列）在时间轴拖动时使用 content 作为标题
    if (level === maxLevel && showTimeline.value && alarmInfo?.content) {
      const titleEl = rootEl.querySelector('.lf-flow-node__title') as HTMLElement | null;
      if (titleEl) titleEl.textContent = String(alarmInfo.content ?? '');
    }

    // 绑定点击事件，保持交互能力，并为后果节点加样式类以增大内边距
    const el = rootEl.querySelector('.lf-flow-node') as HTMLElement | null;
    if (el) {
      if (level === maxLevel) el.classList.add('lf-flow-node--consequence');
      el.addEventListener('click', () => {
        try {
          const {graphModel, model} = this.props as any;
          graphModel?.eventCenter?.emit?.('custom:flow-node-click', model);
        } catch {
        }
      });
    }
  }
}

class FlowNodeModel extends HtmlNodeModel {
  setAttributes() {
    super.setAttributes();
    this.width = 200;

    // 根据节点层级与是否展示 bar 阈值设置高度
    const level = this.properties?.level ?? 0;
    const nodeType = this.properties?.nodeType ?? 0;
    const hasBarText =
      Array.isArray(this.properties?.baseValues) &&
      (this.properties.baseValues as any[]).length > 0 &&
      nodeType !== 1 &&
      nodeType !== 4 &&
      nodeType !== 5;
    let base = level === 0 ? 110 : 100;
    if (hasBarText) base += 5; // 预留空间
    this.height = base;
  }
}

class FlowEdgeModel extends PolylineEdgeModel {
  getEdgeStyle() {
    const style = super.getEdgeStyle();
    return {
      ...style,
      stroke: '#37A8FF',
      strokeDasharray: '6 4',
      strokeWidth: 1.4
    };
  }

  setAttributes() {
    super.setAttributes();
    this.zIndex = 1;
  }
}

class FlowEdgeDangerModel extends PolylineEdgeModel {
  getEdgeStyle() {
    const style = super.getEdgeStyle();
    return {
      ...style,
      stroke: '#FF3737',
      strokeDasharray: '6 4',
      strokeWidth: 1.4
    };
  }

  setAttributes() {
    super.setAttributes();
    this.zIndex = 100; // 设置更高的 z-index 确保置顶
  }
}

function initLogicFlow() {
  if (!logicFlowRef.value) return;
  lf?.destroy();
  lf = new LogicFlow({
    container: logicFlowRef.value,
    grid: false,
    adjustNodePosition: false,
    stopZoomGraph: false,
    stopScrollGraph: false,
    stopMoveGraph: false,
    hoverOutline: false,
    nodeSelectedOutline: false,
    edgeSelectedOutline: false,
    adjustEdge: false,
    nodeTextEdit: false,
    isSilentMode: true
  });

  lf.register({
    type: 'flow-node',
    view: FlowNodeView,
    model: FlowNodeModel
  });

  lf.register({
    type: 'flow-edge',
    view: PolylineEdge,
    model: FlowEdgeModel
  });

  lf.register({
    type: 'flow-edge-danger',
    view: PolylineEdge,
    model: FlowEdgeDangerModel
  });

  // 监听 HtmlNode 发出的自定义点击事件，弹出报警详情
  lf.on('custom:flow-node-click', (model: any) => {
    try {
      const raw = (model?.properties?.label ?? {}) as any;
      let info: any = null;
      if (showTimeline.value) {
        const currentTime = getCurrentTimeFromPercent(timelinePercent.value);
        if (currentTime) info = getNodeAlarmAtTime(raw, currentTime);
      } else if (Array.isArray(raw?.alarms) && raw.alarms.length > 0) {
        info = getLatestAlarmFromList(raw.alarms);
      }
      // 无论是否有报警信息，均弹出弹窗；
      // 若无报警，则提供基础占位信息（位号/节点名可用于识别）
      activeAlarm.value = info || {
        alarmId: undefined,
        indicatorCode: raw?.indicatorCode || raw?.code || raw?.nodeName || '',
        alarmLevel: undefined,
        warnLevel: undefined,
        alarmValue: undefined,
        startTime: undefined,
        duration: undefined,
        combineAlarm: undefined,
        description: '暂无报警',
        reason: undefined
      };
      // 记录节点信息 content1 以便在弹窗中展示
      activeNodeContent1.value = String(raw?.content1 ?? '');
      showAlarmDialog.value = true;
    } catch (e) {
      // ignore
    }
  });

  renderLogicFlow();
}

function renderLogicFlow(shouldResetView = false) {
  if (!lf) return;
  lf.clearData();

  const graphData = currentGraphData.value;
  if (!graphData) return;

  const flow = buildFlowFromGraph(graphData, {
    hGap: 300, // 层间距
    vGap: 150, // 同层节点间距
    left: 200
  });

  const rawNodes = flow.nodes as any[];
  const nodes: NodeConfig[] = rawNodes.map((n) => {
    const copy: any = {...n, properties: {...(n as any).properties}};
    const textValue = (n as any)?.text?.value;
    copy.properties.label = textValue ?? copy?.properties?.label ?? '';
    // 保留 level 信息到 properties 中，用于设置节点高度
    copy.properties.level = textValue?.level ?? 0;
    // 传递 baseValues 与 nodeType，便于在 Model 中决定高度等
    copy.properties.baseValues = Array.isArray(textValue?.baseValues) ? textValue.baseValues : [];
    copy.properties.nodeType = textValue?.nodeType ?? 0;
    if (copy.text) delete copy.text;
    return copy as NodeConfig;
  });

  // 建立节点位置与尺寸映射（用于计算边的拐点）
  const nodeMetaMap = new Map();
  nodes.forEach((node) => {
    const level = Number((node as any)?.properties?.level ?? 0);
    const nodeType = Number((node as any)?.properties?.nodeType ?? 0);
    const hasBarText =
      Array.isArray((node as any)?.properties?.baseValues) &&
      (node as any).properties.baseValues.length > 0 &&
      nodeType !== 1 &&
      nodeType !== 4 &&
      nodeType !== 5;
    // FlowNodeModel 中设置：width=200，高度 = 基础 +（有阈值文本时 +10）
    const width = 200;
    const height = (level === 0 ? 110 : 100) + (hasBarText ? 10 : 0);
    nodeMetaMap.set(String(node.id), {
      x: Number(node.x),
      y: Number(node.y),
      width,
      height,
      level
    });
  });

  // 根据当前时间轴位置动态判断节点是否报警
  const currentTime = getCurrentTimeFromPercent(timelinePercent.value);
  const baseEdges = flow.edges || [];

  // 判断节点是否报警（用于边的颜色与置顶层级）
  const isNodeWarning = (nodeId: string) => {
    const node = rawNodes.find((n: any) => n.id === nodeId);
    const nodeData = (node as any)?.text?.value;
    if (!nodeData) return false;

    if (showTimeline.value && currentTime) {
      const alarm = getNodeAlarmAtTime(nodeData, currentTime);
      return alarm !== null;
    }
    return !!(nodeData.alarms && nodeData.alarms.length > 0);
  };

  // 按源/目标分组边：用于统一拐点列
  const edgesBySource = new Map();
  const edgesByTarget = new Map();
  baseEdges.forEach((edge) => {
    const s = String(edge.sourceNodeId);
    const t = String(edge.targetNodeId);
    if (!edgesBySource.has(s)) edgesBySource.set(s, []);
    if (!edgesByTarget.has(t)) edgesByTarget.set(t, []);
    edgesBySource.get(s)!.push(edge);
    edgesByTarget.get(t)!.push(edge);
  });

  // 分层渲染：普通在下，危险在上
  const normalEdges: EdgeConfig[] = [];
  const dangerEdges: EdgeConfig[] = [];

  baseEdges.forEach((e) => {
    const sourceId = String(e.sourceNodeId);
    const targetId = String(e.targetNodeId);
    const sMeta = nodeMetaMap.get(sourceId);
    const tMeta = nodeMetaMap.get(targetId);

    // 默认类型按源节点是否报警
    const type = isNodeWarning(sourceId) ? 'flow-edge-danger' : 'flow-edge';

    // 若缺少位置信息，退化为直线边
    if (!sMeta || !tMeta) {
      const edge = {...e, type} as EdgeConfig;
      (type === 'flow-edge-danger' ? dangerEdges : normalEdges).push(edge);
      return;
    }

    // 起止点（源右侧中心 -> 目标左侧中心）
    const start = {x: sMeta.x + sMeta.width / 2, y: sMeta.y};
    const end = {x: tMeta.x - tMeta.width / 2, y: tMeta.y};

    const sSiblings = edgesBySource.get(sourceId) || [];
    const tSiblings = edgesByTarget.get(targetId) || [];

    // 计算统一拐点列（与 BarrierIntegrityModel.vue 对齐）
    let pointsList: { x: number; y: number }[];
    if (sSiblings.length > 1) {
      // 多子：取中点列
      const bendX = start.x + (end.x - start.x) * 0.5;
      pointsList = [
        {x: start.x, y: start.y},
        {x: bendX, y: start.y},
        {x: bendX, y: end.y},
        {x: end.x, y: end.y}
      ];
    } else if (tSiblings.length > 1) {
      // 多父：在目标侧预留进入列
      const preEnterOffset = 40;
      const bendX = end.x - preEnterOffset;
      pointsList = [
        {x: start.x, y: start.y},
        {x: bendX, y: start.y},
        {x: bendX, y: end.y},
        {x: end.x, y: end.y}
      ];
    } else {
      // 单对单：直线两点
      pointsList = [
        {x: start.x, y: start.y},
        {x: end.x, y: end.y}
      ];
    }

    const edge = {...e, type, pointsList} as EdgeConfig;
    (type === 'flow-edge-danger' ? dangerEdges : normalEdges).push(edge);
  });

  // 先渲染普通边，再渲染危险边，确保危险边在上层
  const edges: EdgeConfig[] = [...normalEdges, ...dangerEdges];
  lf.render({nodes, edges});

  // 只在需要时重置视图（加载新数据时）
  if (shouldResetView) {
    setTimeout(() => {
      resetView();
    }, 100);
  }
}

// 重置视图到合适位置和缩放
function resetView() {
  if (!lf) return;
  try {
    // 当存在已选位号时，重置后定位到该位号节点
    if (selectedCode.value) {
      lf.resetZoom();
      lf.resetTranslate();
      // 居中到对应位号节点
      focusNodeByIndicatorCode(selectedCode.value);
      return;
    }

    // 全屏时，优先自适应显示全部节点
    if (isFullscreen.value) {
      fitAllToView();
      return;
    }

    // 非全屏且无选中位号时，回到默认视图
    lf.resetZoom();
    lf.resetTranslate();
  } catch (error) {
    console.error('重置视图失败:', error);
  }
}

// 自适应缩放以使所有节点可见（包含一定边距）
function fitAllToView(padding = 40) {
  if (!lf || !logicFlowRef.value) return;
  const graphData = currentGraphData.value;
  if (!graphData || !graphData.nodes?.length) return;

  // 使用与渲染一致的布局参数，重新计算节点坐标
  const layout = buildFlowFromGraph(graphData, {hGap: 300, vGap: 150, left: 200});
  const rawNodes: any[] = (layout.nodes as any[]) || [];
  if (!rawNodes.length) return;

  // 依据 level 计算节点宽高，得到整体包围盒
  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  rawNodes.forEach((n) => {
    const level = Number(n?.text?.value?.level ?? 0);
    const width = 200;
    const height = level === 0 ? 110 : 100;
    const x = Number(n.x);
    const y = Number(n.y);
    const left = x - width / 2;
    const right = x + width / 2;
    const top = y - height / 2;
    const bottom = y + height / 2;
    if (left < minX) minX = left;
    if (right > maxX) maxX = right;
    if (top < minY) minY = top;
    if (bottom > maxY) maxY = bottom;
  });

  if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) return;

  const bboxW = Math.max(1, maxX - minX);
  const bboxH = Math.max(1, maxY - minY);

  const cw = logicFlowRef.value.clientWidth || 0;
  const ch = logicFlowRef.value.clientHeight || 0;
  if (cw <= 0 || ch <= 0) return;

  const scaleX = (cw - padding * 2) / bboxW;
  const scaleY = (ch - padding * 2) / bboxH;
  let targetScale = Math.min(scaleX, scaleY);
  // 适当留白，避免贴边
  targetScale = targetScale * 0.98;
  // 合理范围内裁剪，避免过小/过大
  targetScale = Math.max(0.3, Math.min(2.5, targetScale));

  // 当前缩放
  const {SCALE_X, TRANSLATE_X, TRANSLATE_Y} = (lf as any).getTransform();
  const currentScale = Number(SCALE_X || 1);
  if (currentScale <= 0) return;

  // 调整缩放到期望值（zoom 接受相对倍数）
  const ratio = targetScale / currentScale;
  if (Math.abs(ratio - 1) > 0.001) {
    (lf as any).zoom(ratio);
  }

  // 更新平移，使包围盒中心落到视图中心
  const centerX = (minX + maxX) / 2;
  const centerY = (minY + maxY) / 2;
  const scale = targetScale; // 刚设置的目标缩放
  const newTX = cw / 2 - centerX * scale;
  const newTY = ch / 2 - centerY * scale;
  const dx = newTX - Number(TRANSLATE_X || 0);
  const dy = newTY - Number(TRANSLATE_Y || 0);
  (lf as any).translate(dx, dy);
}

// 放大视图
function zoomIn() {
  if (!lf) return;
  try {
    const transform = lf.getTransform();
    const currentZoom = transform.SCALE_X;
    // 限制最大缩放为2.5倍
    if (currentZoom < 2.5) {
      lf.zoom(1.2); // 每次放大20%
    }
  } catch (error) {
    console.error('放大失败:', error);
  }
}

// 缩小视图
function zoomOut() {
  if (!lf) return;
  try {
    const transform = lf.getTransform();
    const currentZoom = transform.SCALE_X;
    // 限制最小缩放为0.5倍
    if (currentZoom > 0.5) {
      lf.zoom(0.8); // 每次缩小20%
    }
  } catch (error) {
    console.error('缩小失败:', error);
  }
}

// 监听时间轴拖动，重新渲染节点
// 非拖动场景（点击/跳转）立即刷新；拖动中的刷新由节流调度统一控制
watch(timelinePercent, () => {
  if (!dragging) scheduleLFUpdate(true);
});

onMounted(() => {
  initLogicFlow();
  loadRiskStatus();
  loadRiskSummary();
  loadBarrierData();
  loadDeviceList(); // 加载设备列表，完成后会自动加载节点图谱
  // 顶部标签宽度计算与监听
  nextTick(() => {
    computeBarrierItemWidth();
    barrierResizeObs = new ResizeObserver(() => computeBarrierItemWidth());
    if (barrierScrollRef.value) barrierResizeObs.observe(barrierScrollRef.value);
  });
  window.addEventListener('resize', computeBarrierItemWidth);
});

onUnmounted(() => {
  lf?.destroy();
  document.removeEventListener('mousemove', handleDragging);
  document.removeEventListener('mouseup', stopDragging);

  // 清除自动切换定时器
  if (lfUpdateTimer) {
    clearTimeout(lfUpdateTimer);
    lfUpdateTimer = null;
  }
  if (autoSwitchTimer) {
    clearInterval(autoSwitchTimer);
    autoSwitchTimer = null;
  }

  // 清除恢复定时器
  clearResumeTimer();
  // 释放顶部标签监听
  barrierResizeObs?.disconnect();
  barrierResizeObs = null;
  window.removeEventListener('resize', computeBarrierItemWidth);
});

// 任何来源导致的选中项变化，自动将其滚动到中间
watch(activeBarrier, async (id) => {
  if (id == null) return;
  await nextTick();
  scrollBarrierItemIntoCenter(id);
});
</script>

<style scoped lang="scss">
.analysis-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  padding: 0 14px;
  color: #d8ecff;
  font-size: 12px;
  font-weight: 700;
  background: url('@/assets/bi_images/btn.png') no-repeat center / 100% 100%;
  border: none;
  outline: none;
  cursor: pointer;
}

.analysis-content {
  display: grid;
  grid-auto-rows: minmax(28px, auto);
  row-gap: 10px;
  color: #fff;
  padding: 0 20px;
}

.analysis-content .row {
  display: grid;
  grid-template-columns: 90px 1fr;
  column-gap: 12px;
  align-items: center;
}

.analysis-content .label {
  color: #8ad2ff;
}

.analysis-content .value {
  color: #ffffff;
}

// 深度选择器样式，用于覆盖第三方组件样式
.kam-top :deep(.ds-panel__content) {
  padding: 16px;
  background: rgba(8, 28, 60, 0.88);
}

.kam-bottom :deep(.ds-panel__content) {
  padding: 0;
}

// LogicFlow 组件样式
:deep(.lf-flow-node) {
  padding: 8px 12px;
  background: transparent url('@/assets/bi_images/flow-border.png') no-repeat center / 100% 100%;
  border-radius: 8px;
  color: #ffffff;
  font-size: 12px;
  line-height: 1.4;
  text-align: left;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  white-space: normal;
}

// 后果节点：增大内边距，提升信息承载
:deep(.lf-flow-node--consequence) {
  overflow-y: hidden;
}

// 报警节点使用警示边框背景
:deep(.lf-flow-node--warn) {
  background-image: url('@/assets/bi_images/flow-border-wran.png') !important;
}

// 第一列节点使用原因背景
:deep(.lf-flow-node--reason) {
  background-image: url('@/assets/bi_images/flow-border-reason.png') !important;
}

// type为4或5的节点，右内边距为0，上内边距增加5px
:deep(.lf-flow-node--no-right-padding) {
  padding-top: 15px !important;
  padding-right: 0 !important;
  padding-left: 0 !important;
}

// 第一列节点内容垂直居中
:deep(.lf-flow-node__content-wrapper--center) {
  display: flex !important;
  flex-direction: column !important;
  justify-content: center !important;
  align-items: center !important;
}

:deep(.lf-flow-node__inner) {
  white-space: pre-line;
}

// 新的节点内部排版样式
:deep(.lf-flow-node__title) {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 2px;
}

:deep(.lf-flow-node__risk-info) {
  font-size: 11px;
  line-height: 1.4;
  margin-bottom: 2px;
  color: rgba(255, 255, 255, 0.85);
  font-weight: 400;
}

:deep(.lf-flow-node__risk-info .risk-value) {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid;
  border-radius: 2px;
  font-weight: 500;
  min-width: 40px;
  text-align: center;
  /* 背景色、边框色、文字色由内联样式动态设置 */
}

:deep(.lf-flow-node__indicator-wrapper) {
  position: relative;
  margin-bottom: 2px;
}

/* 无条形指示器时的 currentValue 展示（贴在位号/报警值行上方） */
:deep(.lf-flow-node__cv-over) {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: -16px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
  z-index: 12;
}

/* 贴在条形指示器（三角形）正上方的当前值，不再独占一行 */
:deep(.lf-flow-node__bar-value) {
  position: absolute !important;
  transform: translateX(-50%);
  bottom: 18px;
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
  z-index: 12;
}

:deep(.bar-value-1) {
  left: calc((100% / 4) * 0 + (100% / 8));
  color: #30dafe !important;
}

:deep(.bar-value-2) {
  left: calc((100% / 4) * 1 + (100% / 8));
  color: #f8e064;
}

:deep(.bar-value-3) {
  left: calc((100% / 4) * 2 + (100% / 8));
  color: rgba(229, 79, 34, 1);
}

:deep(.bar-value-4) {
  left: calc((100% / 4) * 3 + (100% / 8));
  color: rgba(171, 52, 239, 1);
}

:deep(.lf-flow-node__content) {
  font-size: 12px;
  line-height: 1.3;
  margin-bottom: 2px;
}

:deep(.lf-flow-node__meta) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  line-height: 1.2;
  margin-bottom: 2px;
}

:deep(.lf-flow-node__bars-wrapper) {
  position: relative;
  margin-top: 4px;
  overflow: visible; /* 确保向上偏移可见 */
}

:deep(.lf-flow-node__bars) {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  height: 12px;
}

:deep(.lf-flow-node__bar-indicator) {
  position: absolute;
  bottom: 10px;
  width: 12px;
  height: 8px;
  z-index: 10;
  filter: drop-shadow(0 0 2px currentColor);
}

:deep(.bar-indicator-1) {
  left: calc((100% / 4) * 0 + (100% / 8) - 6px);
  color: rgba(53, 126, 175, 1);
}

:deep(.bar-indicator-2) {
  left: calc((100% / 4) * 1 + (100% / 8) - 6px);
  color: rgba(209, 207, 91, 1);
}

:deep(.bar-indicator-3) {
  left: calc((100% / 4) * 2 + (100% / 8) - 6px);
  color: rgba(229, 79, 34, 1);
}

:deep(.bar-indicator-4) {
  left: calc((100% / 4) * 3 + (100% / 8) - 6px);
  color: rgba(171, 52, 239, 1);
}

:deep(.lf-flow-node__bar) {
  display: block;
  height: 12px;
  border-radius: 2px;
}

:deep(.lf-flow-node__bar.bar-1) {
  background: rgba(53, 126, 175, 0.3);
  border: 1px solid #1b89c8;
}

:deep(.lf-flow-node__bar.bar-2) {
  background: rgba(209, 207, 91, 0.3);
  border: 1px solid #c89b2f;
}

:deep(.lf-flow-node__bar.bar-3) {
  background: rgba(229, 79, 34, 0.3);
  border: 1px solid #a23311;
}

:deep(.lf-flow-node__bar.bar-4) {
  background: rgba(171, 52, 239, 0.3);
  border: 1px solid #6c2fc8;
}

// 报警位号和报警值样式（between-around布局）
:deep(.lf-flow-node__indicator) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  line-height: 1.3;
  color: #30dafe;
  margin-bottom: 2px;
  font-weight: 500;
  gap: 8px;
}

:deep(.lf-flow-node__indicator .indicator-code) {
  flex-shrink: 0;
}

:deep(.lf-flow-node__indicator .indicator-value) {
  flex-shrink: 0;
  font-weight: 600;
}

// 报警原因样式（自动横向滚动，隐藏滚动条）
:deep(.lf-flow-node__reason) {
  font-size: 11px;
  line-height: 1.2;
  color: #ffb366;
  margin-bottom: 4px;
  overflow-x: hidden;
  overflow-y: hidden;
  white-space: nowrap;
  max-width: 100%;
  position: relative;
}

:deep(.lf-flow-node__reason) > span {
  display: inline-block;
  animation: scroll-text 10s linear infinite;
  padding-right: 20px; /* 添加间隔 */
}

@keyframes scroll-text {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-100%);
  }
}

// Bar 激活状态（去除透明度）
:deep(.lf-flow-node__bar.bar-1.lf-flow-node__bar--active) {
  background: rgba(53, 126, 175, 1);
}

:deep(.lf-flow-node__bar.bar-2.lf-flow-node__bar--active) {
  background: rgba(209, 207, 91, 1);
}

:deep(.lf-flow-node__bar.bar-3.lf-flow-node__bar--active) {
  background: rgba(229, 79, 34, 1);
}

:deep(.lf-flow-node__bar.bar-4.lf-flow-node__bar--active) {
  background: rgba(171, 52, 239, 1);
}

// Element Plus select 组件样式覆盖
// 下拉面板样式
:deep(.logicflow-device-select) {
  background: rgba(12, 37, 70, 0.9) !important;
}

:deep(.logicflow-device-select .el-select-dropdown__item) {
  background: rgba(12, 37, 70, 0.9) !important;
  color: #ffffff !important;
}

:deep(.logicflow-device-select .el-select-dropdown__item:hover) {
  background: rgba(48, 124, 188, 0.4) !important;
}

// 选择框本体样式（联锁报警头部 el-select）
:deep(.device-select .el-select__wrapper) {
  height: 40px !important;
  min-height: 40px !important;
  background: url('@/assets/bi_images/rect_1718.png') no-repeat center / 100% 100% !important;
  border: none !important;
  box-shadow: none !important;
  color: #fff;
  padding: 0 12px !important;
}

:deep(.device-select .el-select__selection) {
  align-items: center; /* 垂直居中内容 */
  text-align: center;
  font-size: 14px;
}

/* 调小下拉箭头与后缀区域尺寸 */
:deep(.device-select .el-select__suffix),
:deep(.device-select .el-select__caret) {
  font-size: 14px;
}

/* 下拉面板的字号也略微减小，保持一致性 */
:deep(.logicflow-device-select .el-select-dropdown__item) {
  font-size: 14px !important;
}

// Make LogicFlow canvas transparent and leave space for timeline
.kam-bottom :deep(.lf-graph) {
  background: transparent !important;
}

/* 偏差红色箭头标注 */
.deviation-arrow {
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 10px solid #ff3737; /* 向下指向轴线 */
}

/* 重置视图按钮样式 */
.reset-view-btn {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.reset-view-btn:hover {
  box-shadow: 0 0 12px rgba(48, 218, 254, 0.5);
}

.reset-view-btn:active {
  transform: scale(0.95);
}

/* 缩放按钮样式 */
.zoom-btn {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.zoom-btn:hover {
  box-shadow: 0 0 12px rgba(48, 218, 254, 0.5);
}

.zoom-btn:active {
  transform: scale(0.95);
}

/* 实时预警信息 - 信息条文本省略样式 */
/* 风险状态表格样式 */
.kam-top :deep(.risk-table) {
  background: transparent;
  color: #fff;
}

.kam-top :deep(.risk-table .el-table__inner-wrapper) {
  background: transparent;
}

.kam-top :deep(.risk-table__header) .el-table__cell {
  background: rgba(10, 38, 74, 0.95) !important;
  color: #fff !important;
  font-weight: 700;
}

.kam-top :deep(.risk-table__row .el-table__cell) {
  background: rgba(12, 40, 70, 0.8) !important;
  border-bottom: 1px solid rgba(48, 124, 188, 0.4) !important;
}

.kam-top :deep(.risk-table .el-table__row:hover > td) {
  background: rgba(18, 60, 98, 0.9) !important;
}

/* 头部三列颜色贴近设计图 */
.kam-top :deep(.risk-table .risk-hl) {
  color: #ffed29 !important;
}

.kam-top :deep(.risk-table .risk-hll) {
  color: #ff9393 !important;
}

.kam-top :deep(.risk-table .risk-lock) {
  color: #d99cff !important;
}

/* 风险统计进度条样式 */
.kam-top :deep(.stat-progress) {
  width: 100%;
}

.kam-top :deep(.stat-progress .el-progress-bar__outer) {
  background: #28446d;
  border-radius: 1px;
  display: flex !important;
  align-items: center !important;
}

.kam-top :deep(.stat-progress .el-progress-bar__inner) {
  background: linear-gradient(90deg, #1486db 0%, #00f0ff 100%);
  border-radius: 1px;
  height: 40%;
  top: 50%;
  transform: translateY(-50%);
}

.kam-top .stat-progress-wrap {
  position: relative;
  overflow: visible;
}

.kam-top .stat-progress-wrap > .progress-marker {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border: 1px solid #0aeefe;
  border-radius: 50%;
  box-sizing: border-box;
  pointer-events: none;
  z-index: 100;
}

.kam-top .stat-progress-wrap > .progress-marker > .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 2px 2px rgba(255, 255, 255, 0.8);
}

/* 全屏样式 */
.fullscreen-panel {
  position: fixed !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100vw !important;
  height: 100vh !important;
  z-index: 9999 !important;
  background: rgba(8, 28, 60, 0.98) !important;
  margin: 0 !important;
  padding: 20px !important;
  box-sizing: border-box !important;
  --animate-duration: 0.5s;
}

.fullscreen-panel :deep(.ds-panel__content) {
  height: calc(100% - 50px) !important;
}

/* 顶部横向滚动标签容器 */
</style>
